Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

pyvo

Package Overview
Dependencies
Maintainers
5
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

pyvo - npm Package Compare versions

Comparing version
1.6.2
to
1.7
+5
.github/release.yml
changelog:
exclude:
authors:
- dependabot
- pre-commit-ci

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

***************************************************
MIVOT (``pyvo.mivot``): How to annotate data - Tips
***************************************************
The annotation process is intended to be performed at the server level.
How it is implemented depends on the related DAL protocol, the framework used,
and the available metadata.
This process likely occurs before the data table is streamed out because
the Mivot block must precede the TABLE block.
This means it cannot use the table FIELDs, but rather some internal representation.
However, the examples below use the FIELDs to demonstrate how an annotation task could work.
Map a magnitude to a Mango Brightness property
==============================================
Assuming that our dataset has the two following fields, let's map the magnitude in the J band
to the ``mango:Brightness`` class.
.. code-block:: xml
<FIELD name="Jmag" ucd="phot.mag;em.IR.J" datatype="float" width="6" precision="3" unit="mag">
<DESCRIPTION>?(jmag) 2MASS J-band magnitude</DESCRIPTION>
</FIELD>
<FIELD name="e_Jmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag">
<DESCRIPTION>?(ejmag) Error on Jmag</DESCRIPTION>
</FIELD>
The MANGO brightness class packs together 3 components: the magnitude, its error and the photometric calibration.
Mivot serializations of the photometric calibrations are given by the SVO `Filter Profile Service <https://svo2.cab.inta-csic.es/svo/theory/fps/>`_.
The first thing to do is to get the FPS identifier of the searched filter (2MASS J in our case).
Once the filter is selected, the identifier of the calibration in the desired system can by copied from the
`FPS <https://svo2.cab.inta-csic.es/svo/theory/fps/index.php?id=2MASS/2MASS.J&&mode=browse&gname=2MASS&gname2=2MASS#filter>`_
page as shown below.
.. image:: _images/filterProfileService.png
:width: 500
:alt: FPS screen shot.
Now, we can build the mapping parameters and apply them to add the mapping of that property.
.. code-block:: python
votable = parse("SOME/VOTABLE/PATH")
builder = InstancesFromModels(votable, dmid="URAT1")
# Add the mapping of a brightness property
builder.add_mango_brightness( photcal_id="2MASS/2MASS.J/Vega",
mapping={"value": "Jmag",
"error": { "class": "PErrorSym1D", "sigma": "e_Jmag"}
},
semantics={"description": "magnitude J",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude",
"label": "magnitude"})
# Once all all properties have been mapped, we can
# tell the builder to complete the mapping block
builder.pack_into_votable()
The mapping parameters can be interpreted that way:
- The photometric calibration match the ``2MASS/2MASS.J/Vega`` FPS output
- The magnitude is given by the FIELD identified by ``Jmag``
- The magnitude error, which is symmetrical, is given by the FIELD identified by ``e_Jmag``
- The optional semantics block of the property (see the MANGO specification) indicates that the
property is a magnitude.
Map table data to a Mango EpochPosition property
================================================
The mapping of any property follow the same schema but with specific mapping parameters.
As it turns out, the EpochPosition can be very complex, with six parameters, their errors and their correlations.
If the VOTable fields are available during the annotation process, the API can extract a template of the mapping parameters.
.. code-block:: python
scs_srv = SCSService(" https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main")
query_result = scs_srv.search(
pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'),
radius=0.5)
builder = InstancesFromModels(query_result.votable, dmid="URAT1")
# Get a mapping proposal based on the FIELD UCDs
parameters = builder.extract_epoch_position_parameters()
DictUtils.print_pretty_json(parameters)
The JSON below shows the detected mapping parameters as a dictionary whose structure matches that expected by the API.
.. code-block:: json
{
"frames": {
"spaceSys": {
"dmid": "_spaceframe_ICRS_BARYCENTER"
},
"timeSys": {}
},
"mapping": {
"longitude": "t1_c8",
"latitude": "t1_c9",
"parallax": "t1_c11",
"pmLongitude": "t1_c12",
"pmLatitude": "t1_c13",
"errors": {
"properMotion": {
"class": "PErrorSym2D",
"sigma1": "e_pmRA",
"sigma2": "e_pmDE"
}
},
"correlations": {}
},
"semantics": {
"description": "6 parameters position",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location",
"label": "Astronomical location"
}
}
This template can be updated manually or by any other means, and then used to adjust the "EpochPosition" mapping.
.. code-block:: python
# Add the EpochPosition to the annotations with the modified mapping parameters
builder.add_mango_epoch_position(**parameters)
builder.pack_into_votable()
******************************************************
MIVOT (``pyvo.mivot``): Annotation Writer - Public API
******************************************************
This API allows to easily map VOTable data to the Mango data model (both native and imported components).
Only a little knowledge of the models is required.
Mango is a model designed to enhance the description of table data in a way that each table
row can be interpreted as a Mango Object.
Mango objects are made of a property container, a description of the data origin and links
on other Mango objects (not implemented yet).
In this current implementation, only 3 properties are supported (Epoch position, photometry, and color)
in addition to the data origin that can be added as literal values (not connected with table data).
The pseudo codes below show the way to use this API.
The first step is to create the Annotation Builder and connect it to the VOTable to be annotated.
.. code-block:: python
votable = parse("MY/VOTABLE")
builder = InstancesFromModels(votable, dmid="DR3Name")
The ``dmid`` optional parameter gives the column (ID or name) to be used as identifier of the Mango objects.
The builder is then ready to get the properties to add to the Mango object.
.. code-block:: python
builder.add_mango_magnitude(photcal_id=photcal_id, mapping=mapping, semantics=semantics)
builder.add_mango_magnitude(photcal_id=other_photcal_id, mapping=other_mapping, semantics=other_semantics)
builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics)
builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics)
We can now add the description of the data origin.
.. code-block:: python
builder.add_query_origin(mapping)
- The order in which the components are added does not matter.
- The details of the parameters are described below.
Now the MIVOT block can be completed and inserted into the VOtable.
.. code-block:: python
builder.pack_into_votable()
votable.to_xml("MY/ANNOTATED/VOTABLE")
About Parameters
================
Mappings are always given as dictionaries, where keys are the model roles and values
are either column names or literal values.
The lists of supported roles are given in the :py:mod:`pyvo.mivot.glossary`.
The 3 functions adding properties have all 3 arguments
- ``filter/frame``: Map of the coordinate systems or photometric calibrations that apply to the property.
All values specified here are considered literal.
The corresponding Mivot instances are placed in the GLOBALS block.
- **Photometric filter**: must be given as a filter profile service identifier (filter id followed with the photometric system)
- Identifiers can be found on the SVO `page <http://svo2.cab.inta-csic.es/theory/fps/>`_
(example: ``GAIA/GAIA3.Grp/AB``)
- The FPS service returns a full calibration instance, which is then split into a filter object
and a calibration object that refers to that filter.
- Each one of these components has its own ``dmid`` generated by the API. ``dmid`` can be used to reference them.
Example for filter ``GAIA/GAIA3.Grp/AB``:
- photometric calibration identifer: ``dmid="_photcal_GAIA_GAIA3_Grvs_AB"``
- filter identifier ``dmid="_filter_GAIA_GAIA3_Grvs_AB"``
- **Coordinate systems**: Can be given either by ``dmid`` or by parameters
(see `pyvo.mivot.writer.InstancesFromModels.add_simple_space_frame`
and `pyvo.mivot.writer.InstancesFromModels.add_simple_time_frame`).
- by ``dmid``: Identifiers are generated by the API when a frame is added in the GLOBALS.
(example ``{"dmid": "_spaceframe_spaceRefFrame_equinox_refPosition"}``)
- by parameters: The mapping parameters are given as dictionaries as for the mapping (see below)
(example ``{"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER'}``)
- ``Mapping``: Mapping of the table data to the property attributes.
The fine structure of these dictionaries is specific to each mapped class,
but all follow the same pattern.
Values specified as strings are considered to be column identifiers,
unless the string starts with a '*'. In this case, the stripped string is taken as the literal value.
Other value types (numeric or boolean) are all considered as literals.
+-------------+---------------------------------+
| **value** | **attribute value** |
+=============+=================================+
| "\*M31" | "M31" |
+-------------+---------------------------------+
| "ObjName" | value of the column "ObjName" |
+-------------+---------------------------------+
| True | True |
+-------------+---------------------------------+
| 123456 | 123456 |
+-------------+---------------------------------+
| 45.8987 | 45.8987 |
+-------------+---------------------------------+
- ``semantics``: Semantic tags (text + vocabulary entry) that apply to the property.
+-------------+---------------------------------------------------------+
+ **key** | **value** +
+=============+=========================================================+
+ description + free text description of the property +
+-------------+---------------------------------------------------------+
+ uri + URI of the vocabulary term to which the property refers +
+-------------+---------------------------------------------------------+
+ label + vocabulary term to which the property refers +
+-------------+---------------------------------------------------------+
All ``semantics`` fields are considered literal values.
Add Query origin
----------------
Add the Mango ``QueryOrigin`` instance to the current ``MangoObject``.
.. figure:: _images/mangoDataOrigin.png
:width: 500
DataOrigin package of Mango.
``QueryOrigin`` is the object grouping together all the components needed to model the origin
of the MangoObject. It is always identified with ``dmid="_origin"``
.. code-block:: python
builder.add_query_origin(mapping)
The detail of the ``mapping`` parameter is given in the `pyvo.mivot.writer.InstancesFromModels.add_query_origin` documentation
The ``QueryOrigin`` object can be automatically built from the INFO tags of the VOtable. The success of this operation depends
on the way INFO tags are populated.
The method below, analyzes the INFO tags and insert the resulting query origin into the annotations.
.. code-block:: python
builder.extract_data_origin()
- This code has been optimized to work with Vizier output.
- INFO tags of the VOTable header are analysed to set the ``QueryOrigin`` attributes.
- INFO tags of the header of the first resource are analyzed to set the ``DataOrigin`` objects.
- The automatic mapping does not work for VOtable joining several tables yet.
Add Properties
--------------
The main purpose of the ``MangoObject`` is to collect various properties contained in the data row,
although synthetic properties (literal value only) can be added as well.
- The properties are stored in a container named ``propertyDock``.
- During he annotation process, properties are added one by one by specific methods.
.. figure:: _images/mangoProperties.png
:width: 500
Properties supported by Mango.
Add EpochPosition
^^^^^^^^^^^^^^^^^
The ``EpochPosition`` property describes the astrometry of a moving source.
.. figure:: _images/mangoEpochPosition.png
:width: 500
EpochPosition components of Mango.
It handles six parameters (position, proper motion, parallax and radial velocity) valid at a given epoch
(``obsDate`` field) with their correlations and errors and the coordinate system for both space and time axis.
.. code-block:: python
builder.add_epoch_position(frames, mapping, semantics)
The detail of the parameters is given with the description of the
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position` method.
The first parameter (``frames``) specifies both space and time frames.
It can either contain the frame dmid-s if they are already installed, for instance from TIMESYS and COOSYS tags,
or the mapping elements needed to install them:
Both frames can be automatically extracted from the COOSYS and TIMSYS VOTable elements.
.. code-block:: python
frame_mapping = builder.extract_frames()
builder.add_epoch_position(frame_mapping, mapping, semantics)
This automatic metedata extraction can be extended to any method parameters.
- COOSYS -> coords:SpaceSys
- TIMESYS -> coords:TimeSys
- FIELD -> mango:EpochPosition
.. code-block:: python
epoch_position_mapping = builder.extract_epoch_position_parameters()
builder.add_mango_epoch_position(**epoch_position_mapping)
However, it is advisable to take a look at the automatic mapping, as its content
is highly dependent on the VO table metadata, and the risk of incompleteness,
mismatches or confusion cannot be ruled out.
Add Brightness
^^^^^^^^^^^^^^
The ``Brightness`` binds a flux or a magnitude with an error and a photometric calibration.
.. code-block:: python
builder.add_mango_brightness(photcal_id, mapping, semantics)
The detail of the parameters is given with the
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_brightness` docstring.
Add Color
^^^^^^^^^
The ``Color`` binds to a Color index or an hardness ratio value with an error and two photometric filters.
.. code-block:: python
builder.add_mango_color(filter_ids, mapping, semantics)
The detail of the parameters is given with the
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_color` docstring.
Reference/API
=============
.. automodapi:: pyvo.mivot.writer
.. automodapi:: pyvo.mivot.utils
.. automodapi:: pyvo.mivot.glossary
************************************************************
MIVOT (``pyvo.mivot``): How to use annotated data - Examples
************************************************************
Photometric properties readout
==============================
This example is based on VOTables provided by the ``XTapDB`` service.
This service exposes the slim `4XMM dr14 catalogue <http://xmmssc.irap.omp.eu/>`_.
It is able to map query responses on the fly to the MANGO data model.
The annotation process only annotates the columns that are selected by the query.
The following properties are supported:
- ``mango:Brightness`` to which fluxes are mapped
- ``mango:Color`` to which hardness ratio are mapped
- ``mango:EpochPosition`` to which positions and first observation dates are mapped
- ``mango:Status`` to which quality flags of the source detections are mapped
A specific response format (``application/x-votable+xml;content=mivot``) must be set in order
to tell the server to annotate the queried data.
(*Please read the comment inside the code snippet carefully to fully understand the process*)
.. code-block:: python
import pytest
from pyvo.utils import activate_features
from pyvo.dal import TAPService
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.utils.dict_utils import DictUtils
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
service = TAPService('https://xcatdb.unistra.fr/xtapdb')
result = service.run_sync(
"""
SELECT TOP 5 * FROM "public".mergedentry
""",
format="application/x-votable+xml;content=mivot"
)
# The MIVOT viewer generates the model view of the data
m_viewer = MivotViewer(result, resolve_ref=True)
# Print out the Mivot annotations read out of the VOtable
# This statement is just for a pedagogic purpose (access to a private attribute)
XmlUtils.pretty_print(m_viewer._mapping_block)
In this first step we just queried the service and we built the object that will process the Mivot annotations.
The Mivot block printing output is too long to be listed here. However, the screenshot below shows its shallow structure.
.. image:: _images/xtapdbXML.png
:width: 500
:alt: Shallow structure of the annotation block.
- The GLOBALS section contains all the coordinate systems (in a wide sense). This includes the allowed values for
the detection flags and the photometric calibrations.
- The TEMPLATES section contains the objects to which table data is mapped. In this example, there is one
``MangoObject`` instance which holds all the mapped properties.
At instantiation time, the viewer reads the first data row, which must exist,
in order to construct a Python object that reflects the mapped model.
.. code-block:: python
# Build a Python object matching the TEMPLATES content and
# which leaves are set with the values of the first row
mango_object = m_viewer.dm_instance
# Print out the content of the Python object
# This statement is just for a pedagogic purpose
DictUtils.print_pretty_json(mango_object.to_dict())
The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row.
You can explore the structure of this object by using the printed dictionary or standard object paths as shown below.
Now, we can iterate through the table data and retrieve an updated Mivot instance for each row.
.. code-block:: python
while m_viewer.next_row_view():
if mango_object.dmtype == "mango:MangoObject":
print(f"Read source {mango_object.identifier.value} {mango_object.dmtype}")
for mango_property in mango_object.propertyDock:
if mango_property.dmtype == "mango:Brightness":
if mango_property.value.value:
mag_value = mango_property.value.value
mag_error = mango_property.error.sigma.value
phot_cal = mango_property.photCal
spectral_location = phot_cal.photometryFilter.spectralLocation
mag_filter = phot_cal.identifier.value
spectral_location = phot_cal.photometryFilter.spectralLocation
mag_wl = spectral_location.value.value
sunit = spectral_location.unitexpression.value
print(f" flux at {mag_wl} {sunit} (filter {mag_filter}) is {mag_value:.2e} +/- {mag_error:.2e}")
Read source 4XMM J054329.3-682106 mango:MangoObject
flux at 0.35 keV (filter XMM/EPIC/EB1) is 8.35e-14 +/- 3.15e-14
flux at 0.75 keV (filter XMM/EPIC/EB2) is 3.26e-15 +/- 5.45e-15
flux at 6.1 keV (filter XMM/EPIC/EB8) is 8.68e-14 +/- 6.64e-14
...
...
The same code can easily be connected with matplotlib to plot SEDs as shown below (code not provided).
.. image:: _images/xtapdbSED.png
:width: 500
:alt: XMM SED
It is to noted that the current table row keeps available through the Mivot viewer.
.. code-block:: python
row = m_viewer.table_row
.. important::
The code shown in this example can be used with any VOTable that has data mapped to MANGO.
It contains no features specific to the XtatDB output.
This is exactly the purpose of the MIVOT/MANGO abstraction layer: to allow the same processing
to be applied to any annotated VOTable.
The same client code can be reused in many places with many datasets, provided they are annotated.
EpochPosition property readout
==============================
This example is based on a VOtable resulting on a Vizier cone search.
This service maps the data to the ``EpochPosition`` MANGO property,
which models a full source's astrometry at a given date.
.. warning::
At the time of writing, Vizier only mapped positions and proper motions (when available),
and the definitive epoch class had not been adopted.
Therefore, this implementation may differ a little bit from the standard model.
Vizier does not wrap the source properties in a MANGO object,
but rather lists them in the Mivot *TEMPLATES*.
The annotation reader must support both designs.
In the first step below, we run a standard cone search query by using the standard PyVO API.
.. code-block:: python
import pytest
import astropy.units as u
from astropy.coordinates import SkyCoord
from pyvo.dal.scs import SCSService
from pyvo.utils import activate_features
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder
from pyvo.mivot.utils.dict_utils import DictUtils
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main")
query_result = scs_srv.search(
pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'),
radius=0.5)
# The MIVOt viewer generates the model view of the data
m_viewer = MivotViewer(query_result, resolve_ref=True)
Once the query is finished, we can get a reference to the object that will process the Mivot annotations.
.. code-block:: python
# Build a Python object matching the TEMPLATES content and
# which leaves are set with the values of the first row
mango_property = m_viewer.dm_instance
# Print out the content of the Python object
# This statement is just for a pedagogic purpose
DictUtils.print_pretty_json(mango_property.to_dict())
The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row.
You can explore the structure of this object by using standard object paths or by browsing the dictionary shown below.
.. code-block:: json
{
"dmtype": "mango:EpochPosition",
"longitude": {
"dmtype": "ivoa:RealQuantity",
"value": 51.64272638,
"unit": "deg"
},
"latitude": {
"dmtype": "ivoa:RealQuantity",
"value": 60.28156089,
"unit": "deg"
},
"pmLongitude": {
"dmtype": "ivoa:RealQuantity",
"value": 13.31,
"unit": "mas/yr"
},
"pmLatitude": {
"dmtype": "ivoa:RealQuantity",
"value": -23.43,
"unit": "mas/yr"
},
"epoch": {
"dmtype": "ivoa:RealQuantity",
"value": 1991.25,
"unit": "yr"
},
"parallax": {
"dmtype": "ivoa:RealQuantity",
"value": 5.12,
"unit": "mas"
},
"spaceSys": {
"dmtype": "coords:SpaceSys",
"dmid": "SpaceFrame_ICRS",
"dmrole": "mango:EpochPosition.spaceSys",
"frame": {
"dmrole": "coords:PhysicalCoordSys.frame",
"dmtype": "coords:SpaceFrame",
"spaceRefFrame": {
"dmtype": "ivoa:string",
"value": "ICRS"
}
}
}
}
The reader can transform ``EpochPosition`` instances into ``SkyCoord`` instances.
These can then be used for further scientific processing.
.. code-block:: python
while m_viewer.next_row_view():
if mango_property.dmtype == "mango:EpochPosition":
scb = SkyCoordBuilder(mango_property.to_dict())
# do whatever process with the SkyCoord object
print(scb.build_sky_coord())
.. important::
Similar to the previous example, this code can be used with any VOTable with data mapped to MANGO.
It contains no features specific to the Vizier output.
It avoids the need for users to build SkyCoord objects by hand from VOTable fields,
which is never an easy task.
The next section provides some tips to use the API documented in the annoter `page <annoter.html>`_.
******************************************************
MIVOT (``pyvo.mivot``): Annotation Viewer - Public API
******************************************************
Introduction
============
.. pull-quote::
Model Instances in VOTables (MIVOT) defines a syntax to map VOTable
data to any model serialized in VO-DML. The annotation operates as a
bridge between the data and the model. It associates the column/param
metadata from the VOTable to the data model elements (class, attributes,
types, etc.) [...].
The data model elements are grouped in an independent annotation block
complying with the MIVOT XML syntax. This annotation block is added
as an extra resource element at the top of the VOTable result resource. The
MIVOT syntax allows to describe a data structure as a hierarchy of classes.
It is also able to represent relations and composition between them. It can
also build up data model objects by aggregating instances from different
tables of the VOTable (get more in :doc:`index`).
Using the API
=============
Integrated Readout
------------------
The ``ModelViewer`` module manages access to data mapped to a model through dynamically
generated objects (``MivotInstance`` class).
The example below shows how a VOTable result of a cone-search query can be parsed and data
mapped to the ``EpochPosition`` class.
.. doctest-remote-data::
>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> from pyvo.dal.scs import SCSService
>>> from pyvo.utils.prototype import activate_features
>>> from pyvo.mivot.version_checker import check_astropy_version
>>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer
>>> activate_features("MIVOT")
>>> if check_astropy_version() is False:
... pytest.skip("MIVOT test skipped because of the astropy version.")
>>> scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main")
>>> m_viewer = MivotViewer(
... scs_srv.search(
... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'),
... radius=0.05
... ),
... resolve_ref=True
... )
>>> mivot_instance = m_viewer.dm_instance
>>> print(mivot_instance.dmtype)
mango:EpochPosition
>>> print(mivot_instance.spaceSys.frame.spaceRefFrame.value)
ICRS
>>> while m_viewer.next_row_view():
... print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}")
position: 59.94033461 52.26722684
In this example, the data readout is totally managed by the ``MivotViewer`` instance.
The ``astropy.io.votable`` API is encapsulated in this module.
Model leaves (class attributes) are complex types that provide additional information:
- ``value``: attribute value
- ``dmtype``: attribute type such as defined in the Mivot annotations
- ``unit``: attribute unit such as defined in the Mivot annotations
- ``ref``: identifier of the table column mapped on the attribute
The model view on a data row can also be passed as a Python dictionary
using the ``dict`` property of ``MivotInstance``.
.. code-block:: python
:caption: Working with a model view as a dictionary
(the JSON layout has been squashed for display purpose)
from pyvo.mivot.utils.dict_utils import DictUtils
mivot_instance = m_viewer.dm_instance
mivot_object_dict = mivot_object.dict
DictUtils.print_pretty_json(mivot_object_dict)
{
"dmtype": "EpochPosition",
"longitude": {"value": 359.94372764, "unit": "deg"},
"latitude": {"value": -0.28005255, "unit": "deg"},
"pmLongitude": {"value": -5.14, "unit": "mas/yr"},
"pmLatitude": {"value": -25.43, "unit": "mas/yr"},
"epoch": {"value": 1991.25, "unit": "year"},
"Coordinate_coordSys": {
"dmtype": "SpaceSys",
"dmid": "SpaceFrame_ICRS",
"dmrole": "coordSys",
"spaceRefFrame": {"value": "ICRS"},
},
}
- It is recommended to use a copy of the
dictionary as it will be rebuilt each time the ``dict`` property is invoked.
- The default representation of ``MivotInstance`` instances is made with a pretty
string serialization of this dictionary.
Per-Row Readout
---------------
The annotation schema can also be applied to table rows read outside of the ``MivotViewer``
with the `astropy.io.votable` API:
.. code-block:: python
:caption: Accessing the model view of Astropy table rows
votable = parse(path_to_votable)
table = votable.resources[0].tables[0]
# init the viewer
mivot_viewer = MivotViewer(votable, resource_number=0)
mivot_object = mivot_viewer.dm_instance
# and feed it with the table row
read = []
for rec in table.array:
mivot_object.update(rec)
read.append(mivot_object.longitude.value)
# show that the model retrieve the correct data values
assert rec["RAICRS"] == mivot_object.longitude.value
assert rec["DEICRS"] == mivot_object.latitude.value
In this case, it is up to the user to ensure that the read data rows are those mapped by the Mivot annotations.
For XML Hackers
---------------
The model instances can also be serialized as XML elements that can be parsed with XPath queries.
.. code-block:: python
:caption: Accessing the XML view of the mapped model instances
with MivotViewer(path_to_votable) as mivot_viewer:
while mivot_viewer.next_row_view():
xml_view = mivot_viewer.xml_view
# do whatever you want with this XML element
It is to be noted that ``mivot_viewer.xml_view`` is a shortcut
for ``mivot_viewer.xml_view.view`` where ``mivot_viewer.xml_view``
is is an instance of ``pyvo.mivot.viewer.XmlViewer``.
This object provides many functions facilitating the XML parsing.
Class Generation in a Nutshell
------------------------------
MIVOT reconstructs model structures with 3 elements:
- ``INSTANCE`` for the objects
- ``ATTRIBUTE`` for the attributes
- ``COLLECTION`` for the elements with a cardinality greater than 1
The role played by each of these elements in the model hierarchy is defined
by its ``@dmrole`` XML attribute. Types of both ``INSTANCE`` and ``ATTRIBUTE`` are defined by
their ``@dmtype`` XML attributes.
``MivotInstance`` classes are built by following MIVOT annotation structure:
- ``INSTANCE`` are represented by Python classes
- ``ATTRIBUTE`` are represented by Python class fields
- ``COLLECTION`` are represented by Python lists ([])
``@dmrole`` and ``@dmtype`` cannot be used as Python keywords as such, because they are built from VO-DML
identifiers, which have the following structure: ``model:a.b``.
- Only the last part of the path is kept for attribute names.
- For class names, forbidden characters (``:`` or ``.``) are replaced with ``_``.
- Original ``@dmtype`` are kept as attributes of generated Python objects.
- The structure of the ``MivotInstance`` objects can be inferred from the mapped model in 2 different ways:
- 1. From the MIVOT instance property ``MivotInstance.dict`` a shown above.
This is a pure Python dictionary but its access can be slow because it is generated
on the fly each time the property is invoked.
- 2. From the internal class dictionary ``MivotInstance.__dict__``
(see the Python `data model <https://docs.python.org/3/reference/datamodel.html>`_).
.. code-block:: python
:caption: Exploring the MivotInstance structure with the internal dictionaries
mivot_instance = mivot_viewer.dm_instance
print(mivot_instance.__dict__.keys())
dict_keys(['dmtype', 'longitude', 'latitude', 'pmLongitude', 'pmLatitude', 'epoch', 'Coordinate_coordSys'])
print(mivot_instance.Coordinate_coordSys.__dict__.keys())
dict_keys(['dmtype', 'dmid', 'dmrole', 'spaceRefFrame'])
print(mivot_instance.Coordinate_coordSys.spaceRefFrame.__dict__.keys())
dict_keys(['dmtype', 'value', 'unit', 'ref'])
Reference/API
=============
.. automodapi:: pyvo.mivot.viewer
***************************************************
MIVOT (``pyvo.mivot``): Annotation Writer - Dev API
***************************************************
Introduction
============
.. pull-quote::
Model Instances in VOTables (MIVOT) defines a syntax to map VOTable
data to any model serialized in VO-DML. The annotation operates as a
bridge between the data and the model. It associates the column/param
metadata from the VOTable to the data model elements (class, attributes,
types, etc.) [...].
The data model elements are grouped in an independent annotation block
complying with the MIVOT XML syntax. This annotation block is added
as an extra resource element at the top of the VOTable result resource. The
MIVOT syntax allows to describe a data structure as a hierarchy of classes.
It is also able to represent relations and composition between them. It can
also build up data model objects by aggregating instances from different
tables of the VOTable (get more in :doc:`index`).
- Model Instances in VOTables is a VO `standard <https://ivoa.net/documents/MIVOT/20230620/REC-mivot-1.0.pdf>`_
- Requires Astropy>=6.0
- ``pyvo.mivot`` is a prototype feature which must be activated with ``activate_features("MIVOT")``
Use the API
===========
Build Annotation Object per Object
----------------------------------
This documentation is intended for developers of data model classes who want to map them to VOTables
and not for end users. A future version will allow end users to create annotations with
ready-to-use data model building blocks.
Creating annotations consists of 3 steps:
#. Create individual instances (INSTANCE) using the ``MivotInstance`` class: objects are
built attribute by attribute. These components can then be aggregated into
more complex objects following the structure of the mapped model(s).
#. Wrap the annotations with the ``MivotAnnotations`` class: declare to the annotation builder
the models used, and place individual instances at the right place (TEMPLATES or GLOBALS).
#. Insert the annotations into a VOtable by using the Astropy API (wrapped in the package logic).
The annotation builder does not check whether the XML conforms to any particular model.
It simply validates it against the MIVOT XML Schema if the ``xmlvalidator`` package if is installed.
The example below shows a step-by-step construction of a MIVOT block mapping
a position with its error (as defined in the ``MANGO`` draft)
and its space coordinate system (as defined in the ``Coordinates`` model and imported by ``MANGO``).
Build the empty MIVOT Block
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The MIVOT block consists of:
- A process status
- A list of mapped models
- A list of globals, which are objects not associated with
VOTable data and that can be shared by any other MIVOT instance.
- A list of templates, which are objects that are connected to
VOTable data and whose leaf values change from one row to another.
- ``MIVOT`` is still an experimental feature which must be activated
.. code-block:: python
from astropy.io.votable import parse
from pyvo.utils import activate_features
from pyvo.mivot.utils.exceptions import MappingException
from pyvo.mivot.utils.dict_utils import DictUtils
from pyvo.mivot.writer.annotations import MivotAnnotations
from pyvo.mivot.writer.instance import MivotInstance
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
activate_features("MIVOT")
mivot_annotations = MivotAnnotations()
mivot_annotations.add_model(
"ivoa", "https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml"
)
mivot_annotations.add_model(
"coords", "https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml"
)
mivot_annotations.add_model(
"mango",
"https://raw.githubusercontent.com/lmichel/MANGO/draft-0.1/vo-dml/mango.vo-dml.xml",
)
mivot_annotations.set_report(True, "PyVO Tuto")
Build the Coordinate System Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The space coordinate system is made of a space frame and a reference position, both wrapped in a ``coords:SpaceSys``
object (see the `Coordinates <https://ivoa.net/documents/Coords/20221004/index.html>`_ data model).
The time coordinate system is made of a time frame and a reference position, both wrapped in a ``coords:TimeSys``
object.
- Each of these objects have a ``dmid`` which will be used as a reference by the ``EpochPosition`` instance.
.. code-block:: python
mivot_annotations.add_simple_space_frame(ref_frame="FK5",
ref_position="BARYCENTER",
equinox="J2000",
dmid="_spacesys"
)
mivot_annotations.add_simple_time_frame(ref_frame="TCB",
ref_position="BARYCENTER",
dmid="_timesys"
)
Build the EpochPosition Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- In this example we only use the position attributes (RA/DEC) of the ``EpochPosition`` class.
- The reference to the space coordinate system is added at the end.
- The ``ref`` XML attributes reference columns that must be used to set the model attributes.
Their values depend on the VOTable to be mapped.
.. code-block:: python
from astropy.io.votable import parse
from pyvo.utils import activate_features
from pyvo.mivot.utils.exceptions import MappingException
from pyvo.mivot.utils.dict_utils import DictUtils
from pyvo.mivot.writer.annotations import MivotAnnotations
from pyvo.mivot.writer.instance import MivotInstance
from pyvo.mivot.viewer.mivot_viewer import MivotViewer
activate_features("MIVOT")
position = MivotInstance(dmtype="mango:EpochPosition")
position.add_attribute(
dmtype="ivoa:RealQuantity",
dmrole="mango:EpochPosition.longitude",
unit="deg",
ref="RAICRS",
)
position.add_attribute(
dmtype="ivoa:RealQuantity",
dmrole="mango:EpochPosition.latitude",
unit="deg",
ref="DEICRS",
)
position.add_reference(
dmref="_spacesys", dmrole="mango:EpochPosition.spaceSys"
)
Build the Position Error
^^^^^^^^^^^^^^^^^^^^^^^^
- We assume that the position error is the same on both axes without correlation.
In terms of MANGO error, this corresponds to a 2x2 diagonal error matrix with two equal coefficients.
- Finally, the error is added as a component of the ``EpochPosition`` instance.
.. code-block:: python
epoch_position_error = MivotInstance(
dmtype="mango:EpochPositionErrors", dmrole="mango:EpochPosition.errors"
)
position_error = MivotInstance(
dmtype="mango:error.ErrorCorrMatrix",
dmrole="mango:EpochPositionErrors.position",
)
position_error.add_attribute(
dmtype="ivoa:RealQuantity",
dmrole="mango:error.ErrorCorrMatrix.sigma1",
unit="arcsec",
ref="sigm",
)
position_error.add_attribute(
dmtype="ivoa:RealQuantity",
dmrole="mango:error.ErrorCorrMatrix.sigma2",
unit="arcsec",
ref="sigm",
)
epoch_position_error.add_instance(position_error)
position.add_instance(epoch_position_error)
Pack the MIVOT Block
^^^^^^^^^^^^^^^^^^^^
- Pack the model instances previously built.
- The latest step (build_mivot_block) includes a validation of the MIVOT syntax that works only
if the ``xmlvalidator`` package has been installed.
.. code-block:: python
mivot_annotations.add_templates(position)
mivot_annotations.build_mivot_block()
Insert the MIVOT Block in a VOTable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- This straightforward step is based on the Astropy VOTable API.
- Annotations are stored in-memory (in the parsed VOtable).
- The mapping can be tested with the ``MivotViewer`` API (see the :doc:`viewer`)
- The VOtable must be explicitly saved on disk if needed.
.. code-block:: python
from astropy.io.votable import parse
votable = parse(votable_path)
mivot_annotations.insert_into_votable(votable)
mivot_viewer = MivotViewer(votable)
mapped_instance = mivot_viewer.dm_instance
votable.to_xml("pyvo-tuto.xml")
Validate the annotations against the models
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- This action requires the ``mivot-validatorXXX`` package to be installed.
- It validates the mapped classes against the models they come from.
.. code-block:: shell
% pip install mivot-validator
% mivot-instance-validate pyvo-tuto.xml
...
Valid if no error message
...
"""
Glossary for the MIVOT package
- Model related words (hard-coded for now)
- URLs
"""
__all__ = ["Url", "IvoaType", "Roles", "CoordSystems", "ModelPrefix",
"VodmlUrl", "EpochPositionAutoMapping"]
class Url:
"""
Service URL(s) that are used by the API
"""
#: Filter Profile Service URL (SVO)
FPS = (
"http://svo2.cab.inta-csic.es/svo/theory/fps/fpsmivot.php?PhotCalID="
)
class IvoaType:
"""
Primitive VODML types
"""
#: primitive type for strings
string = "ivoa:string"
#: primitive type for reals
real = "ivoa:real"
#: primitive type for real quantity (real + unit)
RealQuantity = "ivoa:RealQuantity"
#: primitive type for booleans
bool = "ivoa:boolean"
#: primitive type for a point in time
datetime = "ivoa:datatime"
class Roles:
"""
Accepted roles for all implemented classes;
correspond to the last path element of the ``dmroles``
as defined in VODML (VODML-ID)
"""
#: Roles of the EpochPosition class that are supported
EpochPosition = [
"longitude",
"latitude",
"parallax",
"radialVelocity",
"pmLongitude",
"pmLatitude",
"obsDate",
]
#: Roles of the EpochPositionCorrelations class that are supported
EpochPositionCorrelations = [
"longitudeParallax",
"latitudeParallax",
"pmLongitudeParallax",
"pmLatitudeParallax",
"longitudeLatitude",
"pmLongitudePmLatitude",
"latitudePmLatitude",
"latitudePmLongitude",
"longitudePmLatitude",
"longitudePmLongitude",
"isCovariance",
]
#: Roles of the EpochPositionErrors class that are supported
EpochPositionErrors = [
"parallax",
"radialVelocity",
"position",
"properMotion",
]
#: Roles of the PErrorSym1D class that is supported
PErrorSym1D = [
"sigma"
]
#: Roles of the PErrorAsym1D class that are supported
PErrorAsym1D = [
"low",
"high"
]
#: Roles of the PErrorSym2D class that are supported
PErrorSym2D = [
"sigma1",
"sigma2"
]
#: Roles of the PErrorSym2D class that are supported
PErrorEllipse = [
"semiMajorAxis",
"semiMinorAxis",
"angle"
]
#: Roles of the PhotometricProperty class that are supported
PhotometricProperty = ["value",
"error"
]
#: Roles of the Color class that are supported
Color = ["value",
"error",
"definition"
]
#: Roles of the QueryOrigin class that are supported
QueryOrigin = ["publisher", "server_software", "service_protocol",
"request", "request_date", "query", "contact", "ivoid"
]
#: Roles of the DataOrigin class that are supported
DataOrigin = ["ivoid", "reference_url", "resource_version", "creators",
"cites", "is_derived_from", "original_date", "rights", "rights_uri", "articles"
]
#: Roles of the Article class that are supported
Article = ["identifier", "editor"
]
class CoordSystems:
"""
Supported values for the coordinate system parameters (space and time)
"""
#: see IVOA `refframe <https://www.ivoa.net/rdf/refframe/2022-02-22/refframe.html>`_ vocabulary
space_frames = ["eq_FK4", "FK4", "eq_FK5", "FK5", "ICRS", "GALACTIC", "SUPER_GALACTIC", "ECLIPTIC"]
#: see IVOA `refposition <https://www.ivoa.net/rdf/refposition/2019-03-15/refposition.html>`_ vocabulary
ref_positions = ["BARYCENTER", "GEOCENTER", "TOPOCENTER"]
#: see IVOA `timescale <https://www.ivoa.net/rdf/timescale/2019-03-15/timescale.html>`_ vocabulary
time_frames = ["TAI", "TT", "TDT", "ET", "IAT", "UT1",
"UTC", "GMT", "GPS", "TCG", "TCB", "TBD", "LOCAL"]
#: supported time formats (could be replaced witha vocabulary later on)
time_formats = ["byear", "cxcsec", "decimalyear", "fits",
"gps", "iso", "timestamp", "jyear", "year", "jd", "mjd"]
class ModelPrefix:
"""
Model names as defined in VODML
"""
#: `VODML <https://www.ivoa.net/documents/VODML/20180910/index.html>`_ primitive types
ivoa = "ivoa"
#: VODML prefix of the MANGO model
mango = "mango"
#: VODML prefix of the `Photometry Data Model
#: <https://www.ivoa.net/documents/PHOTDM/20221101/index.html>`_
Phot = "Phot"
#: VODML prefix of the Astronomical `Astronomical Coordinates and Coordinate Systems
#: <https://www.ivoa.net/documents/Coords/20221004/index.html>`_ datamodel
coords = "coords"
#: VODML prefix of the `Astronomical Measurements Model
#: <https://www.ivoa.net/documents/Meas/20221004/index.html>`_
meas = "meas"
class VodmlUrl:
"""
VODML URLs of the supported models.
Names of the class attributes match the `ModelPrefix` fields.
"""
#: VODML URL of the `VODML
#: <https://www.ivoa.net/documents/VODML/20180910/index.html>`_ primitive types
ivoa = "https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml"
#: VODML URL of the MANGO model
mango = "https://raw.githubusercontent.com/ivoa-std/MANGO/refs/heads/wd-v1.0/vo-dml/mango.vo-dml.xml"
#: VODML URL of the `Photometry Data Model <https://www.ivoa.net/documents/PHOTDM/20221101/index.html>`_
Phot = "https://ivoa.net/xml/VODML/Phot-v1.vodml.xml"
#: VODML URL of the `Astronomical Coordinates and Coordinate Systems
#: <https://www.ivoa.net/documents/Coords/20221004/index.html>`_ datamodel
coords = "https://ivoa.net/xml/VODML/Coords-v1.vo-dml.xml"
#: VODML URL of the `Astronomical Measurements Model
#: <https://www.ivoa.net/documents/Meas/20221004/index.html>`_
meas = "https://ivoa.net/xml/VODML/Meas-v1.vo-dml.xml"
class EpochPositionAutoMapping:
"""
Expected UCDs for identifying FIELD to be mapped to EpochPosition attributes.
- UCD-s of the associated errors are derived from them
- list items must have an exact match
- Single values are evaluated as starting with
"""
#: UCD-s accepted to map the longitude
longitude = ["POS_EQ_RA_MAIN", "pos.eq.ra;meta.main"]
#: UCD-s accepted to map the latitude
latitude = ["POS_EQ_DEC_MAIN", "pos.eq.dec;meta.main"]
#: UCD-s accepted to map the proper motion longitude
pmLongitude = ["pos.pm;pos.eq.ra"]
#: UCD-s accepted to map the proper motion latitude
pmLatitude = ["pos.pm;pos.eq.dec"]
#: UCD-s accepted to map the obsDate
obsDate = ["time.epoch;obs;stat.mean", "time.epoch;obs"]
#: UCD-s accepted to map the parallax
parallax = ["pos.parallax.trig"]
#: first word of UCD-s accepted to map the radial velocity
radialVelocity = "spect.dopplerVeloc.opt"
from contextlib import contextmanager
import pytest
import requests_mock
class ContextAdapter(requests_mock.Adapter):
"""
requests_mock adapter where ``register_uri`` returns a context manager
"""
@contextmanager
def register_uri(self, *args, **kwargs):
matcher = super().register_uri(*args, **kwargs)
yield matcher
self.remove_matcher(matcher)
def remove_matcher(self, matcher):
if matcher in self._matchers:
self._matchers.remove(matcher)
@pytest.fixture(scope='function')
def mocker():
with requests_mock.Mocker(
adapter=ContextAdapter(case_sensitive=True)
) as mocker_ins:
yield mocker_ins
<?xml version="1.0"?>
<INSTANCE dmrole="" dmtype="Phot:PhotCal">
<ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grp/AB" />
<INSTANCE dmrole="Phot:PhotCal.zeroPoint" dmtype="Phot:ZeroPoint">
<ATTRIBUTE dmrole="Phot:ZeroPoint.type" dmtype="ivoa:integer" value="0" />
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeValue" dmtype="ivoa:real" value="0" />
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeError" dmtype="ivoa:real" value="0" />
<ATTRIBUTE dmrole="Phot:ZeroPoint.softeningParameter" dmtype="ivoa:real" value="" />
<INSTANCE dmrole="Phot:ZeroPoint.flux" dmtype="Phot:Flux">
<ATTRIBUTE dmrole="Phot:Flux.ucd" dmtype="Phot:UCD" value="phot.flux.density" />
<ATTRIBUTE dmrole="Phot:Flux.unitexpression" dmtype="ivoa:Unit" value="Jy" />
<ATTRIBUTE dmrole="Phot:Flux.value" dmtype="ivoa:real" value="3631" />
<ATTRIBUTE dmrole="Phot:Flux.error" dmtype="ivoa:real" value="0" />
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem">
<ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="AB" />
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.photometryFilter" dmtype="Phot:photometryFilter">
<ATTRIBUTE dmrole="Phot:PhotometryFilter.fpsIdentifier" dmtype="ivoa:string" value="ivo://svo/fps" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grp" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="GAIA3.Grp" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="GAIA Grp filter, DR3" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="" />
<INSTANCE dmrole="Phot:PhotCal.photometryFilter.bandwidth" dmtype="Phot:Bandwidth">
<ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth" />
<ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value="" />
<ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="2924.4362576966" />
<ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="6200.4566154641" />
<ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="6200.4566154641" />
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve">
<INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access">
<ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI"
value="http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=GAIA/GAIA3.Grp" />
<ATTRIBUTE dmrole="Phot:Access.size" dmtype="ivoa:integer" value="10" />
<ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="application/x-votable+xml" />
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation">
<ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl;meta.main" />
<ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="Angstrom" />
<ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="7769.0226260418" />
</INSTANCE>
</INSTANCE>
</INSTANCE>
<?xml version="1.0"?>
<INSTANCE dmrole="" dmtype="Phot:PhotCal">
<ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grvs/AB" />
<INSTANCE dmrole="Phot:PhotCal.zeroPoint" dmtype="Phot:ZeroPoint">
<ATTRIBUTE dmrole="Phot:ZeroPoint.type" dmtype="ivoa:integer" value="0" />
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeValue" dmtype="ivoa:real" value="0" />
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeError" dmtype="ivoa:real" value="0" />
<ATTRIBUTE dmrole="Phot:ZeroPoint.softeningParameter" dmtype="ivoa:real" value="" />
<INSTANCE dmrole="Phot:ZeroPoint.flux" dmtype="Phot:Flux">
<ATTRIBUTE dmrole="Phot:Flux.ucd" dmtype="Phot:UCD" value="phot.flux.density" />
<ATTRIBUTE dmrole="Phot:Flux.unitexpression" dmtype="ivoa:Unit" value="Jy" />
<ATTRIBUTE dmrole="Phot:Flux.value" dmtype="ivoa:real" value="3631" />
<ATTRIBUTE dmrole="Phot:Flux.error" dmtype="ivoa:real" value="0" />
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem">
<ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="AB" />
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.photometryFilter" dmtype="Phot:photometryFilter">
<ATTRIBUTE dmrole="Phot:PhotometryFilter.fpsIdentifier" dmtype="ivoa:string" value="ivo://svo/fps" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grvs" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="GAIA3.Grvs" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="GAIA Grvs filter, DR3" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="" />
<INSTANCE dmrole="Phot:PhotCal.photometryFilter.bandwidth" dmtype="Phot:Bandwidth">
<ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth" />
<ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value="" />
<ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="235.1243119935" />
<ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="8459.7502595805" />
<ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="8459.7502595805" />
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve">
<INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access">
<ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI"
value="http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=GAIA/GAIA3.Grvs" />
<ATTRIBUTE dmrole="Phot:Access.size" dmtype="ivoa:integer" value="24" />
<ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="application/x-votable+xml" />
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation">
<ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl;meta.main" />
<ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="Angstrom" />
<ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="8578.7613420633" />
</INSTANCE>
</INSTANCE>
</INSTANCE>
<VODML xmlns="http://www.ivoa.net/xml/mivot">
<REPORT status="OK"/>
<MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml"/>
<MODEL name="mango" url="https://raw.githubusercontent.com/ivoa-std/MANGO/refs/heads/wd-v1.0/vo-dml/mango.vo-dml.xml"/>
<MODEL name="Phot" url="https://ivoa.net/xml/VODML/Phot-v1.vodml.xml"/>
<MODEL name="coords" url="https://ivoa.net/xml/VODML/Coords-v1.vo-dml.xml"/>
<GLOBALS>
<INSTANCE dmtype="mango:origin.QueryOrigin" dmid="_origin">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.service_protocol" value="ASU"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.request_date" value="2024-03-21T15:16:08"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.request" value="https://vizier.cds.unistra.fr/viz-bin/votable?-oc.form=dec&amp;amp;-out.max=5&amp;amp;-out.add=_r&amp;amp;-out.add=_RAJ,_DEJ&amp;amp;-sort=_r&amp;amp;-c.eq=J2000&amp;amp;-c.r= 2&amp;amp;-c.u=arcmin&amp;amp;-c.geom=r&amp;amp;-source=I/355/gaiadr3&amp;amp;-order=I&amp;amp;-out.orig=standard&amp;amp;-out=DR3Name&amp;amp;-out=RA_ICRS&amp;amp;-out=DE_ICRS&amp;amp;-out=Source&amp;amp;-out=e_RA_ICRS&amp;amp;-out=e_DE_ICRS&amp;amp;-out=Plx&amp;amp;-out=e_Plx&amp;amp;-out=PM&amp;amp;-out=pmRA&amp;amp;-out=e_pmRA&amp;amp;-out=pmDE&amp;amp;-out=e_pmDE&amp;amp;-out=RADEcor&amp;amp;-out=RAPlxcor&amp;amp;-out=RApmRAcor&amp;amp;-out=RApmDEcor&amp;amp;-out=DEPlxcor&amp;amp;-out=DEpmRAcor&amp;amp;-out=DEpmDEcor&amp;amp;-out=PlxpmRAcor&amp;amp;-out=PlxpmDEcor&amp;amp;-out=pmRApmDEcor&amp;amp;-out=RV&amp;amp;-out=e_RV&amp;amp;-out=Vbroad&amp;amp;-out=GRVSmag&amp;amp;-out=QSO&amp;amp;-out=Gal&amp;amp;-out=NSS&amp;amp;-out=XPcont&amp;amp;-out=XPsamp&amp;amp;-out=RVS&amp;amp;-out=EpochPh&amp;amp;-out=EpochRV&amp;amp;-out=MCMCGSP&amp;amp;-out=MCMCMSC&amp;amp;-out=And&amp;amp;-out=Teff&amp;amp;-out=logg&amp;amp;-out=[Fe/H]&amp;amp;-out=Dist&amp;amp;-out=A0&amp;amp;-out=HIP&amp;amp;-out=PS1&amp;amp;-out=SDSS13&amp;amp;-out=SKYM2&amp;amp;-out=TYC2&amp;amp;-out=URAT1&amp;amp;-out=AllWISE&amp;amp;-out=APASS9&amp;amp;-out=GSC23&amp;amp;-out=RAVE5&amp;amp;-out=2MASS&amp;amp;-out=RAVE6&amp;amp;-out=RAJ2000&amp;amp;-out=DEJ2000&amp;amp;"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.contact" value="cds-question@unistra.fr"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.server_software" value="7.33.3"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.publisher" value="CDS"/>
<COLLECTION dmrole="mango:origin.QueryOrigin.dataOrigin">
<INSTANCE dmtype="mango:origin.DataOrigin">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.ivoid" value="ivo://cds.vizier/i/355"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.cites" value="bibcode:2022yCat.1355....0G, doi:10.26093/cds/vizier.1355"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.original_date" value="2022"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.reference_url" value="https://cdsarc.cds.unistra.fr/viz-bin/cat/I/355"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.rights_uri" value="https://cds.unistra.fr/vizier-org/licences_vizier.html"/>
<COLLECTION dmrole="mango:origin.DataOrigin.articles">
<INSTANCE dmtype="mango:origin.Article">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.Article.identifier" value="doi:10.1051/0004-6361/202039657e]"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.Article.editor" value="A&amp;A"/>
</INSTANCE>
</COLLECTION>
<COLLECTION dmrole="mango:origin.DataOrigin.creators">
<ATTRIBUTE dmtype="ivoa:string" value="Gaia collaboration"/>
</COLLECTION>
</INSTANCE>
</COLLECTION>
</INSTANCE>
<INSTANCE dmtype="Phot:PhotCal" dmid="_photcal_GAIA_GAIA3_Grvs_AB">
<ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grvs/AB"/>
<INSTANCE dmrole="Phot:PhotCal.zeroPoint" dmtype="Phot:ZeroPoint">
<ATTRIBUTE dmrole="Phot:ZeroPoint.type" dmtype="ivoa:integer" value="0"/>
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeValue" dmtype="ivoa:real" value="0"/>
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeError" dmtype="ivoa:real" value="0"/>
<!-- ATTRIBUTE dmrole="Phot:ZeroPoint.softeningParameter" dmtype="ivoa:real" value=""/ -->
<INSTANCE dmrole="Phot:ZeroPoint.flux" dmtype="Phot:Flux">
<ATTRIBUTE dmrole="Phot:Flux.ucd" dmtype="Phot:UCD" value="phot.flux.density"/>
<ATTRIBUTE dmrole="Phot:Flux.unitexpression" dmtype="ivoa:Unit" value="Jy"/>
<ATTRIBUTE dmrole="Phot:Flux.value" dmtype="ivoa:real" value="3631"/>
<ATTRIBUTE dmrole="Phot:Flux.error" dmtype="ivoa:real" value="0"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem">
<ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="AB"/>
</INSTANCE>
<REFERENCE dmrole="Phot:PhotCal.photometryFilter" dmref="_photfilter_GAIA_GAIA3_Grvs_AB"/>
</INSTANCE>
<INSTANCE dmtype="Phot:PhotometryFilter" dmid="_photfilter_GAIA_GAIA3_Grvs_AB">
<ATTRIBUTE dmrole="Phot:PhotometryFilter.fpsIdentifier" dmtype="ivoa:string" value="ivo://svo/fps"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grvs"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="GAIA3.Grvs"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="GAIA Grvs filter, DR3"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value=""/>
<INSTANCE dmrole="Phot:PhotometryFilter.bandwidth" dmtype="Phot:Bandwidth">
<ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value=""/>
<ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="235.1243119935"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="8459.7502595805"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="8459.7502595805"/>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve">
<INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access">
<ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI" value="http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=GAIA/GAIA3.Grvs"/>
<ATTRIBUTE dmrole="Phot:Access.size" dmtype="ivoa:integer" value="24"/>
<ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="application/x-votable+xml"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation">
<ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl;meta.main"/>
<ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="Angstrom"/>
<ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="8578.7613420633"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="Phot:PhotCal" dmid="_photcal_GAIA_GAIA3_Grp_AB">
<ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grp/AB"/>
<INSTANCE dmrole="Phot:PhotCal.zeroPoint" dmtype="Phot:ZeroPoint">
<ATTRIBUTE dmrole="Phot:ZeroPoint.type" dmtype="ivoa:integer" value="0"/>
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeValue" dmtype="ivoa:real" value="0"/>
<ATTRIBUTE dmrole="Phot:ZeroPoint.referenceMagnitudeError" dmtype="ivoa:real" value="0"/>
<!-- ATTRIBUTE dmrole="Phot:ZeroPoint.softeningParameter" dmtype="ivoa:real" value=""/ -->
<INSTANCE dmrole="Phot:ZeroPoint.flux" dmtype="Phot:Flux">
<ATTRIBUTE dmrole="Phot:Flux.ucd" dmtype="Phot:UCD" value="phot.flux.density"/>
<ATTRIBUTE dmrole="Phot:Flux.unitexpression" dmtype="ivoa:Unit" value="Jy"/>
<ATTRIBUTE dmrole="Phot:Flux.value" dmtype="ivoa:real" value="3631"/>
<ATTRIBUTE dmrole="Phot:Flux.error" dmtype="ivoa:real" value="0"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem">
<ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="AB"/>
</INSTANCE>
<REFERENCE dmrole="Phot:PhotCal.photometryFilter" dmref="_photfilter_GAIA_GAIA3_Grp_AB"/>
</INSTANCE>
<INSTANCE dmtype="Phot:PhotometryFilter" dmid="_photfilter_GAIA_GAIA3_Grp_AB">
<ATTRIBUTE dmrole="Phot:PhotometryFilter.fpsIdentifier" dmtype="ivoa:string" value="ivo://svo/fps"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="GAIA/GAIA3.Grp"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="GAIA3.Grp"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="GAIA Grp filter, DR3"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value=""/>
<INSTANCE dmrole="Phot:PhotometryFilter.bandwidth" dmtype="Phot:Bandwidth">
<ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value=""/>
<ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="2924.4362576966"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="6200.4566154641"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="6200.4566154641"/>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve">
<INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access">
<ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI" value="http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=GAIA/GAIA3.Grp"/>
<ATTRIBUTE dmrole="Phot:Access.size" dmtype="ivoa:integer" value="10"/>
<ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="application/x-votable+xml"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation">
<ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl;meta.main"/>
<ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="Angstrom"/>
<ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="7769.0226260418"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="coords:SpaceSys" dmid="_spaceframe_spaceRefFrame_equinox_refPosition">
<INSTANCE dmtype="coords:SpaceFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:SpaceFrame.spaceRefFrame" value="spaceRefFrame"/>
<ATTRIBUTE dmtype="coords:Epoch" dmrole="coords:SpaceFrame.equinox" value="equinox"/>
<INSTANCE dmtype="coords:StdRefLocation" dmrole="coords:SpaceFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:StdRefLocation.position" value="refPosition"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="coords:TimeSys" dmid="_timeframe_TCB_BARYCENTER">
<INSTANCE dmtype="coords:TimeFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:TimeFrame.timescale" value="TCB"/>
<INSTANCE dmtype="coords:StdRefLocation" dmrole="coords:TimeFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:StdRefLocation.position" value="BARYCENTER"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
</GLOBALS>
<TEMPLATES>
<INSTANCE dmtype="mango:MangoObject" dmid="DR3Name">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:MangoObject.identifier" ref="DR3Name"/>
<REFERENCE dmrole="mango:MangoObject.queryOrigin" dmref="_origin"/>
<COLLECTION dmrole="mango:MangoObject.propertyDock">
<INSTANCE dmtype="mango:Color">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:Property.description" value="very nice color"/>
<INSTANCE dmtype="mango:VocabularyTerm" dmrole="mango:Property.semantics">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.uri" value="vocabulary#term"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.label" value="term"/>
</INSTANCE>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:PhotometricProperty.value" value="8.76"/>
<INSTANCE dmtype="mango:error.PErrorAsym1D" dmrole="mango:PhotometricProperty.error">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorAsym1D.low" value="1"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorAsym1D.high" value="3"/>
</INSTANCE>
<INSTANCE dmtype="mango:ColorDef" dmrole="mango:Color.colorDef">
<ATTRIBUTE dmtype="mango:ColorDefinition" dmrole="mango:ColorDef.definition" value="ColorIndex"/>
<REFERENCE dmrole="mango:ColorDef.low" dmref="_photfilter_GAIA_GAIA3_Grvs_AB"/>
<REFERENCE dmrole="mango:ColorDef.high" dmref="_photfilter_GAIA_GAIA3_Grp_AB"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="mango:Brightness">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:Property.description" value="Grvs magnitude"/>
<INSTANCE dmtype="mango:VocabularyTerm" dmrole="mango:Property.semantics">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.uri" value="https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.label" value="magnitude"/>
</INSTANCE>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:PhotometricProperty.value" unit="mag" ref="GRVSmag"/>
<INSTANCE dmtype="mango:error.PErrorAsym1D" dmrole="mango:PhotometricProperty.error">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorAsym1D.low" value="1"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorAsym1D.high" value="3"/>
</INSTANCE>
<REFERENCE dmrole="mango:Brightness.photCal" dmref="_photcal_GAIA_GAIA3_Grvs_AB"/>
</INSTANCE>
<INSTANCE dmtype="mango:EpochPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:Property.description" value="6 parameters position"/>
<INSTANCE dmtype="mango:VocabularyTerm" dmrole="mango:Property.semantics">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.uri" value="https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.label" value="Astronomical location"/>
</INSTANCE>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.longitude" unit="deg" ref="_RAJ2000"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.latitude" unit="deg" ref="_DEJ2000"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.pmLongitude" unit="mas / yr" ref="pmRA"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.pmLatitude" unit="mas / yr" ref="pmDE"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.parallax" unit="mas" ref="Plx"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.radialVelocity" unit="km / s" ref="RV"/>
<INSTANCE dmtype="mango:DateTime" dmrole="mango:EpochPosition.obsDate">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:DateTime.representation" value="mjd"/>
<ATTRIBUTE dmtype="ivoa:datatime" dmrole="mango:DateTime.dateTime" value="579887.6"/>
</INSTANCE>
<INSTANCE dmtype="mango:EpochPositionCorrelations" dmrole="mango:EpochPosition.correlations">
<ATTRIBUTE dmtype="ivoa:boolean" dmrole="mango:EpochPositionCorrelations.isCovariance" value="True"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.longitudeLatitude" ref="RADEcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.latitudePmLongitude" ref="DEpmRAcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.latitudePmLatitude" ref="DEpmDEcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.longitudePmLongitude" ref="RApmRAcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.longitudePmLatitude" ref="RApmDEcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.longitudeParallax" ref="RAPlxcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.latitudeParallax" ref="DEPlxcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.pmLongitudeParallax" ref="PlxpmRAcor"/>
<ATTRIBUTE dmtype="ivoa:real" dmrole="mango:EpochPositionCorrelations.pmLatitudeParallax" ref="PlxpmDEcor"/>
</INSTANCE>
<INSTANCE dmtype="mango:EpochPositionErrors" dmrole="mango:EpochPosition.errors">
<INSTANCE dmtype="mango:error.PErrorSym2D" dmrole="mango:EpochPositionErrors.position">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma1" unit="mas" ref="e_RA_ICRS"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma2" unit="mas" ref="e_DE_ICRS"/>
</INSTANCE>
<INSTANCE dmtype="mango:error.PErrorSym2D" dmrole="mango:EpochPositionErrors.properMotion">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma1" unit="mas / yr" ref="e_pmRA"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma2" unit="mas / yr" ref="e_pmDE"/>
</INSTANCE>
<INSTANCE dmtype="mango:error.PErrorSym1D" dmrole="mango:EpochPositionErrors.parallax">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym1D.sigma" unit="mas" ref="e_Plx"/>
</INSTANCE>
<INSTANCE dmtype="mango:error.PErrorSym1D" dmrole="mango:EpochPositionErrors.radialVelocity">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym1D.sigma" unit="km / s" ref="e_RV"/>
</INSTANCE>
</INSTANCE>
<REFERENCE dmrole="mango:EpochPosition.spaceSys" dmref="_spaceframe_spaceRefFrame_equinox_refPosition"/>
<REFERENCE dmrole="mango:EpochPosition.timeSys" dmref="_timeframe_TCB_BARYCENTER"/>
</INSTANCE>
</COLLECTION>
</INSTANCE>
</TEMPLATES>
</VODML>
<VODML xmlns="http://www.ivoa.net/xml/mivot">
<REPORT status="OK"/>
<MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml"/>
<MODEL name="coords" url="https://ivoa.net/xml/VODML/Coords-v1.vo-dml.xml"/>
<MODEL name="mango" url="https://raw.githubusercontent.com/ivoa-std/MANGO/refs/heads/wd-v1.0/vo-dml/mango.vo-dml.xml"/>
<GLOBALS>
<INSTANCE dmtype="coords:SpaceSys" dmid="_spaceframe_ICRS_BARYCENTER">
<INSTANCE dmtype="coords:SpaceFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:SpaceFrame.spaceRefFrame" value="ICRS"/>
<INSTANCE dmtype="coords:CustomRefLocation" dmrole="coords:SpaceFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:CustomRefLocation.position" value="BARYCENTER"/>
<ATTRIBUTE dmtype="coords:Epoch" dmrole="coords:CustomRefLocation.epoch" value="2016.000"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="coords:SpaceSys" dmid="_spaceframe_eq_FK5_J2000_BARYCENTER">
<INSTANCE dmtype="coords:SpaceFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:SpaceFrame.spaceRefFrame" value="eq_FK5"/>
<ATTRIBUTE dmtype="coords:Epoch" dmrole="coords:SpaceFrame.equinox" value="J2000"/>
<INSTANCE dmtype="coords:StdRefLocation" dmrole="coords:SpaceFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:StdRefLocation.position" value="BARYCENTER"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="mango:origin.QueryOrigin" dmid="_origin">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.service_protocol" value="ASU"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.request_date" value="2024-03-21T15:16:08"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.request" value="https://vizier.cds.unistra.fr/viz-bin/votable?-oc.form=dec&amp;-out.max=5&amp;-out.add=_r&amp;-out.add=_RAJ,_DEJ&amp;-sort=_r&amp;-c.eq=J2000&amp;-c.r= 2&amp;-c.u=arcmin&amp;-c.geom=r&amp;-source=I/355/gaiadr3&amp;-order=I&amp;-out.orig=standard&amp;-out=DR3Name&amp;-out=RA_ICRS&amp;-out=DE_ICRS&amp;-out=Source&amp;-out=e_RA_ICRS&amp;-out=e_DE_ICRS&amp;-out=Plx&amp;-out=e_Plx&amp;-out=PM&amp;-out=pmRA&amp;-out=e_pmRA&amp;-out=pmDE&amp;-out=e_pmDE&amp;-out=RADEcor&amp;-out=RAPlxcor&amp;-out=RApmRAcor&amp;-out=RApmDEcor&amp;-out=DEPlxcor&amp;-out=DEpmRAcor&amp;-out=DEpmDEcor&amp;-out=PlxpmRAcor&amp;-out=PlxpmDEcor&amp;-out=pmRApmDEcor&amp;-out=RV&amp;-out=e_RV&amp;-out=Vbroad&amp;-out=GRVSmag&amp;-out=QSO&amp;-out=Gal&amp;-out=NSS&amp;-out=XPcont&amp;-out=XPsamp&amp;-out=RVS&amp;-out=EpochPh&amp;-out=EpochRV&amp;-out=MCMCGSP&amp;-out=MCMCMSC&amp;-out=And&amp;-out=Teff&amp;-out=logg&amp;-out=[Fe/H]&amp;-out=Dist&amp;-out=A0&amp;-out=HIP&amp;-out=PS1&amp;-out=SDSS13&amp;-out=SKYM2&amp;-out=TYC2&amp;-out=URAT1&amp;-out=AllWISE&amp;-out=APASS9&amp;-out=GSC23&amp;-out=RAVE5&amp;-out=2MASS&amp;-out=RAVE6&amp;-out=RAJ2000&amp;-out=DEJ2000&amp;"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.contact" value="cds-question@unistra.fr"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.server_software" value="7.33.3"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.QueryOrigin.publisher" value="CDS"/>
<COLLECTION dmrole="mango:origin.QueryOrigin.dataOrigin">
<INSTANCE dmtype="mango:origin.DataOrigin">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.ivoid" value="ivo://cds.vizier/i/355"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.cites" value="bibcode:2022yCat.1355....0G"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.original_date" value="2022"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.reference_url" value="https://cdsarc.cds.unistra.fr/viz-bin/cat/I/355"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:origin.DataOrigin.rights_uri" value="https://cds.unistra.fr/vizier-org/licences_vizier.html"/>
<COLLECTION dmrole="mango:origin.DataOrigin.creators">
<ATTRIBUTE dmtype="ivoa:string" value="Gaia collaboration"/>
</COLLECTION>
</INSTANCE>
</COLLECTION>
</INSTANCE>
</GLOBALS>
<TEMPLATES>
<INSTANCE dmtype="mango:MangoObject" dmid="URAT1">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:MangoObject.identifier" ref="URAT1"/>
<REFERENCE dmrole="mango:MangoObject.queryOrigin" dmref="_origin"/>
<COLLECTION dmrole="mango:MangoObject.propertyDock">
<INSTANCE dmtype="mango:EpochPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:Property.description" value="6 parameters position"/>
<INSTANCE dmtype="mango:VocabularyTerm" dmrole="mango:Property.semantics">
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.uri" value="https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location"/>
<ATTRIBUTE dmtype="ivoa:string" dmrole="mango:VocabularyTerm.label" value="Astronomical location"/>
</INSTANCE>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.longitude" unit="deg" ref="RA_ICRS"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.latitude" unit="deg" ref="DE_ICRS"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.parallax" unit="mas" ref="Plx"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.pmLongitude" unit="mas / yr" ref="pmRA"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.pmLatitude" unit="mas / yr" ref="pmDE"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.radialVelocity" unit="km / s" ref="RV"/>
<INSTANCE dmtype="mango:EpochPositionCorrelations" dmrole="mango:EpochPosition.correlations">
</INSTANCE>
<INSTANCE dmtype="mango:EpochPositionErrors" dmrole="mango:EpochPosition.errors">
<INSTANCE dmtype="mango:error.PErrorSym2D" dmrole="mango:EpochPositionErrors.position">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma1" unit="mas" ref="e_RA_ICRS"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma2" unit="mas" ref="e_DE_ICRS"/>
</INSTANCE>
<INSTANCE dmtype="mango:error.PErrorSym1D" dmrole="mango:EpochPositionErrors.parallax">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym1D.sigma" unit="mas" ref="e_Plx"/>
</INSTANCE>
<INSTANCE dmtype="mango:error.PErrorSym2D" dmrole="mango:EpochPositionErrors.properMotion">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma1" unit="mas / yr" ref="e_pmRA"/>
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym2D.sigma2" unit="mas / yr" ref="e_pmDE"/>
</INSTANCE>
<INSTANCE dmtype="mango:error.PErrorSym1D" dmrole="mango:EpochPositionErrors.radialVelocity">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.PErrorSym1D.sigma" unit="km / s" ref="e_RV"/>
</INSTANCE>
</INSTANCE>
<REFERENCE dmrole="mango:EpochPosition.spaceSys" dmref="_spaceframe_ICRS_BARYCENTER"/>
</INSTANCE>
</COLLECTION>
</INSTANCE>
</TEMPLATES>
</VODML>
<VODML xmlns="http://www.ivoa.net/xml/mivot">
<REPORT status="OK">Generated by pyvo.mivot.writer</REPORT>
<MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" />
<MODEL name="coords" url="https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml" />
<GLOBALS>
<INSTANCE dmtype="coords:SpaceSys" dmid="_spaceframe_FK5_J2000_BARYCENTER">
<INSTANCE dmtype="coords:SpaceFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:SpaceFrame.spaceRefFrame" value="FK5" />
<ATTRIBUTE dmtype="coords:Epoch" dmrole="coords:SpaceFrame.equinox" value="J2000" />
<INSTANCE dmtype="coords:StdRefLocation" dmrole="coords:SpaceFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:StdRefLocation.position" value="BARYCENTER" />
</INSTANCE>
</INSTANCE>
</INSTANCE>
<INSTANCE dmtype="coords:TimeSys" dmid="_timeframe_TCB_BARYCENTER">
<INSTANCE dmtype="coords:TimeFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:TimeFrame.timescale" value="TCB" />
<INSTANCE dmtype="coords:StdRefLocation" dmrole="coords:TimeFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:StdRefLocation.position" value="BARYCENTER" />
</INSTANCE>
</INSTANCE>
</INSTANCE>
</GLOBALS>
</VODML>
<VODML xmlns="http://www.ivoa.net/xml/mivot">
<REPORT status="OK">Generated by pyvo.mivot.writer</REPORT>
<MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" />
<MODEL name="Phot" url="https://ivoa.net/xml/VODML/Phot-v1.vodml.xml" />
<GLOBALS>
<INSTANCE dmtype="Phot:PhotCal" dmid="_photcal_SLOAN_SDSS_g_AB">
<ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="SLOAN/SDSS.g/AB" />
<INSTANCE dmrole="Phot:PhotCal.zeroPoint" dmtype="Phot:AsinhZeroPoint">
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.type" dmtype="ivoa:integer" value="1" />
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.referenceMagnitudeValue" dmtype="ivoa:real" value="0" />
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.referenceMagnitudeError" dmtype="ivoa:real" value="0" />
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.softeningParameter" dmtype="ivoa:real" value="0.00000000009" />
<INSTANCE dmrole="Phot:AsinhZeroPoint.flux" dmtype="Phot:Flux">
<ATTRIBUTE dmrole="Phot:Flux.ucd" dmtype="Phot:UCD" value="phot.flux.density" />
<ATTRIBUTE dmrole="Phot:Flux.unitexpression" dmtype="ivoa:Unit" value="Jy" />
<ATTRIBUTE dmrole="Phot:Flux.value" dmtype="ivoa:real" value="3631" />
<ATTRIBUTE dmrole="Phot:Flux.error" dmtype="ivoa:real" value="0" />
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem">
<ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="AB" />
</INSTANCE>
<REFERENCE dmrole="Phot:PhotCal.photometryFilter" dmref="_photfilter_SLOAN_SDSS_g_AB" />
</INSTANCE>
<INSTANCE dmtype="Phot:photometryFilter" dmid="_photfilter_SLOAN_SDSS_g_AB">
<ATTRIBUTE dmrole="Phot:PhotometryFilter.fpsIdentifier" dmtype="ivoa:string" value="ivo://svo/fps" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="SLOAN/SDSS.g" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="SDSS.g" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="SDSS g full transmission" />
<ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="g" />
<INSTANCE dmrole="Phot:PhotCal.photometryFilter.bandwidth" dmtype="Phot:Bandwidth">
<ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth" />
<ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value="" />
<ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="1064.6831251068" />
<ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="3797.6384743979" />
<ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="3797.6384743979" />
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve">
<INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access">
<ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI"
value="http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=SLOAN/SDSS.g" />
<ATTRIBUTE dmrole="Phot:Access.size" dmtype="ivoa:integer" value="2" />
<ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="application/x-votable+xml" />
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation">
<ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl;meta.main" />
<ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="Angstrom" />
<ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="4702.4953002767" />
</INSTANCE>
</INSTANCE>
</GLOBALS>
</VODML>
{
"dmtype": "mango:EpochPosition",
"longitude": {
"value": 52.2340018,
"unit": "deg"
},
"latitude": {
"value": 59.8937333,
"unit": "deg"
},
"errors": {
"dmtype": "mango:EpochPositionErrors",
"dmrole": "mango:EpochPosition.errors",
"position": {
"dmtype": "mango:error.ErrorCorrMatrix",
"dmrole": "mango:EpochPositionErrors.position",
"sigma1": {
"value": 6.0,
"unit": "arcsec"
},
"sigma2": {
"value": 6.0,
"unit": "arcsec"
}
}
},
"spaceSys": {
"dmtype": "coords:SpaceSys",
"dmid": "_spacesys_icrs",
"dmrole": "mango:EpochPosition.spaceSys",
"frame": {
"dmtype": "coords:SpaceFrame",
"dmrole": "coords:PhysicalCoordSys.frame",
"spaceRefFrame": {
"value": "ICRS"
},
"refPosition": {
"dmtype": "coords:StdRefLocation",
"dmrole": "coords:SpaceFrame.refPosition",
"position": {
"value": "BARYCENTER"
}
}
}
}
}
<VODML xmlns="http://www.ivoa.net/xml/mivot">
<REPORT status="OK">Mivot writer unit test</REPORT>
<MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" />
<MODEL name="coords" url="https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml" />
<MODEL name="mango" url="https://raw.githubusercontent.com/lmichel/MANGO/draft-0.1/vo-dml/mango.vo-dml.xml" />
<GLOBALS>
<INSTANCE dmtype="coords:SpaceSys" dmid="_spacesys_icrs">
<INSTANCE dmtype="coords:SpaceFrame" dmrole="coords:PhysicalCoordSys.frame">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:SpaceFrame.spaceRefFrame" value="ICRS" />
<INSTANCE dmtype="coords:StdRefLocation" dmrole="coords:SpaceFrame.refPosition">
<ATTRIBUTE dmtype="ivoa:string" dmrole="coords:StdRefLocation.position" value="BARYCENTER" />
</INSTANCE>
</INSTANCE>
</INSTANCE>
</GLOBALS>
<TEMPLATES>
<INSTANCE dmtype="mango:EpochPosition">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.longitude" unit="deg" ref="RAICRS" />
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:EpochPosition.latitude" unit="deg" ref="DEICRS" />
<REFERENCE dmrole="mango:EpochPosition.spaceSys" dmref="_spacesys_icrs" />
<INSTANCE dmtype="mango:EpochPositionErrors" dmrole="mango:EpochPosition.errors">
<INSTANCE dmtype="mango:error.ErrorCorrMatrix" dmrole="mango:EpochPositionErrors.position">
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.ErrorCorrMatrix.sigma1" unit="arcsec" ref="sigm" />
<ATTRIBUTE dmtype="ivoa:RealQuantity" dmrole="mango:error.ErrorCorrMatrix.sigma2" unit="arcsec" ref="sigm" />
</INSTANCE>
</INSTANCE>
</INSTANCE>
</TEMPLATES>
</VODML>
<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ivoa.net/xml/VOTable/v1.3"
xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/v1.3">
<DESCRIPTION>
VizieR Astronomical Server vizier.cds.unistra.fr
Date: 2024-03-21T15:16:08 [V7.33.3]
Explanations and Statistics of
UCDs: See LINK below
In case of problem, please report to: cds-question@unistra.fr
</DESCRIPTION>
<!-- VOTable description at http://www.ivoa.net/Documents/latest/VOT.html -->
<INFO name="service_protocol" value="ASU"> IVOID of the protocol through which the data was retrieved</INFO>
<INFO name="request_date" value="2024-03-21T15:16:08"> Query execution date</INFO>
<INFO name="request"
value="https://vizier.cds.unistra.fr/viz-bin/votable?-oc.form=dec&amp;-out.max=5&amp;-out.add=_r&amp;-out.add=_RAJ,_DEJ&amp;-sort=_r&amp;-c.eq=J2000&amp;-c.r= 2&amp;-c.u=arcmin&amp;-c.geom=r&amp;-source=I/355/gaiadr3&amp;-order=I&amp;-out.orig=standard&amp;-out=DR3Name&amp;-out=RA_ICRS&amp;-out=DE_ICRS&amp;-out=Source&amp;-out=e_RA_ICRS&amp;-out=e_DE_ICRS&amp;-out=Plx&amp;-out=e_Plx&amp;-out=PM&amp;-out=pmRA&amp;-out=e_pmRA&amp;-out=pmDE&amp;-out=e_pmDE&amp;-out=RADEcor&amp;-out=RAPlxcor&amp;-out=RApmRAcor&amp;-out=RApmDEcor&amp;-out=DEPlxcor&amp;-out=DEpmRAcor&amp;-out=DEpmDEcor&amp;-out=PlxpmRAcor&amp;-out=PlxpmDEcor&amp;-out=pmRApmDEcor&amp;-out=RV&amp;-out=e_RV&amp;-out=Vbroad&amp;-out=GRVSmag&amp;-out=QSO&amp;-out=Gal&amp;-out=NSS&amp;-out=XPcont&amp;-out=XPsamp&amp;-out=RVS&amp;-out=EpochPh&amp;-out=EpochRV&amp;-out=MCMCGSP&amp;-out=MCMCMSC&amp;-out=And&amp;-out=Teff&amp;-out=logg&amp;-out=[Fe/H]&amp;-out=Dist&amp;-out=A0&amp;-out=HIP&amp;-out=PS1&amp;-out=SDSS13&amp;-out=SKYM2&amp;-out=TYC2&amp;-out=URAT1&amp;-out=AllWISE&amp;-out=APASS9&amp;-out=GSC23&amp;-out=RAVE5&amp;-out=2MASS&amp;-out=RAVE6&amp;-out=RAJ2000&amp;-out=DEJ2000&amp;"> Full request URL (POST)</INFO>
<INFO name="contact" value="cds-question@unistra.fr"> Email or URL to contact publisher</INFO>
<INFO name="server_software" value="7.33.3"> Software version</INFO>
<INFO name="publisher" value="CDS"> Data centre that produced the VOTable</INFO>
<!-- Execution Reports -->
<RESOURCE ID="yCat_1355" name="I/355">
<DESCRIPTION>Gaia DR3 Part 1. Main source (Gaia Collaboration, 2022)</DESCRIPTION>
<INFO name="ivoid" value="ivo://cds.vizier/i/355"> IVOID of underlying data collection </INFO>
<INFO name="creator" value="Gaia collaboration"> First author or institution </INFO>
<INFO name="cites" value="bibcode:2022yCat.1355....0G"> Bibcode of the dataset </INFO>
<INFO name="original_date" value="2022"> Year of the article publication </INFO>
<INFO name="reference_url" value="https://cdsarc.cds.unistra.fr/viz-bin/cat/I/355"> Dataset landing page </INFO>
<INFO name="citation" value="doi:10.26093/cds/vizier.1355"> Dataset identifier that can be used for citation </INFO>
<INFO name="publication_date" value="2024-01-24"> Date of first publication in the data centre </INFO>
<INFO name="rights_uri" value="https://cds.unistra.fr/vizier-org/licences_vizier.html"> Licence URI </INFO>
<COOSYS ID="H_2016.000" system="ICRS" epoch="2016.000" />
<COOSYS ID="J2000" system="eq_FK5" equinox="J2000" />
<RESOURCE type="meta">
<VODML xmlns="http://www.ivoa.net/xml/mivot">
<REPORT status="OK">hand-made mapping</REPORT>
<MODEL name="ivoa" url="https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" />
<MODEL name="coords" url="https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml" />
<MODEL name="meas" url="https://www.ivoa.net/xml/Meas/20200908/Meas-v1.0.vo-dml.xml" />
<MODEL name="mango" url="https://raw.githubusercontent.com/lmichel/MANGO/draft-0.1/vo-dml/mango.vo-dml.xml" />
<GLOBALS>
<INSTANCE dmid="_spacesys_icrs" dmrole="" dmtype="coords:SpaceSys">
<INSTANCE dmrole="coords:PhysicalCoordSys.frame" dmtype="coords:SpaceFrame">
<ATTRIBUTE dmrole="coords:SpaceFrame.spaceRefFrame" dmtype="ivoa:string" value="ICRS" />
<INSTANCE dmrole="coords:SpaceFrame.refPosition" dmtype="coords:StdRefLocation">
<ATTRIBUTE dmrole="coords:StdRefLocation.position" dmtype="ivoa:string" value="BARYCENTER"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
<!-- Not sure about the time frame used for the GAIA catalog -->
<INSTANCE dmid="_timesys_tcb" dmrole="" dmtype="coords:TimeSys">
<INSTANCE dmrole="coords:PhysicalCoordSys.frame" dmtype="coords:TimeFrame">
<ATTRIBUTE dmrole="coords:TimeFrame.timescale" dmtype="ivoa:string" value="TCB" />
<INSTANCE dmrole="coords:TimeFrame.refPosition" dmtype="coords:StdRefLocation">
<ATTRIBUTE dmrole="coords:StdRefLocation.position" dmtype="ivoa:string" value="BARYCENTER"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
</GLOBALS>
<TEMPLATES>
<INSTANCE dmtype="mango:EpochPosition">
<!-- This class is a view of \texttt{Astronomical Coordinates and Coordinate Systems} components that have been put together
to form a consistent description of the position of an object moving over time. It consists of a celestial position, a proper motion,
a radial velocity and a parallax. All components share the same spatial coordinate system. \begin{itemize} \item Both position and
proper motion reuse the \texttt{coords:LonLatPoint} elements. \item The space coordinate system is imported from \texttt{coords:spaceSys}.
\end{itemize} The error is specific to this class as it must support covariance and correlation between the components. " -->
<ATTRIBUTE dmrole="mango:EpochPosition.longitude" dmtype="ivoa:RealQuantity" ref="RA_ICRS" unit="deg"/>
<ATTRIBUTE dmrole="mango:EpochPosition.latitude" dmtype="ivoa:RealQuantity" ref="DE_ICRS" unit="deg"/>
<ATTRIBUTE dmrole="mango:EpochPosition.parallax" dmtype="ivoa:RealQuantity" unit="mas" ref="Plx" />
<ATTRIBUTE dmrole="mango:EpochPosition.radialVelocity" dmtype="ivoa:RealQuantity" unit="km/s" ref="RV" />
<ATTRIBUTE dmrole="mango:EpochPosition.pmLongitude" dmtype="ivoa:RealQuantity" unit="mas/yr" ref="pmRA" />
<ATTRIBUTE dmrole="mango:EpochPosition.pmLatitude" dmtype="ivoa:RealQuantity" unit="mas/yr" ref="pmDE" />
<ATTRIBUTE dmrole="mango:EpochPosition.epoch" dmtype="coords:Epoch" value="2016.5" />
<ATTRIBUTE dmrole="mango:EpochPosition.pmCosLat_applied" dmtype="ivoa:boolean" value="true" />
<!-- Errors on individual quantities -->
<INSTANCE dmrole="mango:EpochPosition.errors" dmtype="mango:EpochPositionErrors">
<!-- Error on parallax -->
<INSTANCE dmrole="mango:EpochPositionErrors.parallax" dmtype="mango:error.PropertyError1D">
<ATTRIBUTE dmrole="mango:error.PropertyError1D.sigma" dmtype="ivoa:RealQuantity" unit="mas" ref="e_Plx" />
</INSTANCE>
<!-- Error on radial velocity -->
<INSTANCE dmrole="mango:EpochPositionErrors.radialVelocity" dmtype="mango:error.PropertyError1D">
<ATTRIBUTE dmrole="mango:error.PropertyError1D.sigma" dmtype="ivoa:RealQuantity" unit="km/s" ref="e_RV" />
</INSTANCE>
<!-- Error on position (diagonal matrix) -->
<INSTANCE dmrole="mango:EpochPositionErrors.position" dmtype="mango:error.ErrorCorrMatrix">
<!-- Error matrix for 2D quantities" -->
<ATTRIBUTE dmrole="mango:error.ErrorCorrMatrix.sigma1" dmtype="ivoa:RealQuantity" unit="mas" ref="e_RA_ICRS" />
<ATTRIBUTE dmrole="mango:error.ErrorCorrMatrix.sigma2" dmtype="ivoa:RealQuantity" unit="mas" ref="e_DE_ICRS" />
</INSTANCE>
<!-- Error on proper motion (diagonal matrix) -->
<INSTANCE dmrole="mango:EpochPositionErrors.properMotion" dmtype="mango:error.ErrorCorrMatrix">
<!-- Error matrix for 2D quantities" -->
<ATTRIBUTE dmrole="mango:error.ErrorCorrMatrix.sigma1" dmtype="ivoa:RealQuantity" unit="mas/yr" ref="e_pmRA" />
<ATTRIBUTE dmrole="mango:error.ErrorCorrMatrix.sigma2" dmtype="ivoa:RealQuantity" unit="mas/yr" ref="e_pmDE" />
</INSTANCE>
</INSTANCE>
<!-- Correlation between quantities -->
<INSTANCE dmrole="mango:EpochPosition.correlations" dmtype="mango:EpochPositionCorrelations">
<!-- Position/proper-motion correlation -->
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.isCovariance" dmtype="ivoa:boolean" value="false" />
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.latitudePmLongitude" dmtype="ivoa:real" ref="DEpmRAcor" />
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.latitudePmLatitude" dmtype="ivoa:real" ref="DEpmDEcor" />
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.longitudePmLongitude" dmtype="ivoa:real" ref="RApmRAcor" />
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.longitudePmLatitude" dmtype="ivoa:real" ref="RApmDEcor" />
<!-- parallax/proper-motion correlation -->
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.pmLongitudeParallax" dmtype="ivoa:real" ref="PlxpmRAcor" />
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.pmLatitudeParallax" dmtype="ivoa:real" ref="PlxpmDEcor" />
<!-- position/parallax correlation -->
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.longitudeParallax" dmtype="ivoa:real" ref="RAPlxcor" />
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.latitudeParallax" dmtype="ivoa:real" ref="DEPlxcor" />
<!-- position/position correlation -->
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.longitudeLatitude" dmtype="ivoa:real" ref="RADEcor" />
<!-- proper-motion/proper-motion correlation -->
<ATTRIBUTE dmrole="mango:EpochPositionCorrelations.pmLongitudePmLatitude" dmtype="ivoa:real" ref="pmRApmDEcor" />
</INSTANCE>
<REFERENCE dmref="_spacesys_icrs" dmrole="mango:EpochPosition.spaceSys" />
<REFERENCE dmref="_timesys_tcb" dmrole="mango:EpochPosition.timeSys" />
</INSTANCE>
</TEMPLATES>
</VODML>
</RESOURCE>
<TABLE ID="I_355_gaiadr3" name="I/355/gaiadr3">
<DESCRIPTION>Gaia DR3 source catalog (1811709771 sources)</DESCRIPTION>
<!-- Gaia data release 3 (Gaia DR3). (\originalcolumnnames)\vizContent{timeSerie}\vizContent{spectrum} -->
<!-- Definitions of GROUPs and FIELDs -->
<FIELD name="_RAJ2000" ucd="pos.eq.ra" ref="J2000" datatype="double" width="17" precision="13" unit="deg"><!-- ucd="POS_EQ_RA" -->
<DESCRIPTION>Right ascension (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account </DESCRIPTION>
</FIELD>
<FIELD name="_DEJ2000" ucd="pos.eq.dec" ref="J2000" datatype="double" width="17" precision="13" unit="deg"><!-- ucd="POS_EQ_DEC" -->
<DESCRIPTION>Declination (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account </DESCRIPTION>
</FIELD>
<FIELD name="DR3Name" ucd="meta.id" datatype="char" arraysize="28*"><!-- ucd="ID_ALTERNATIVE" -->
<DESCRIPTION>Unique source designation (unique across all Data Releases) (designation)</DESCRIPTION>
</FIELD>
<FIELD name="RA_ICRS" ucd="pos.eq.ra;meta.main" ref="H_2016.000" datatype="double" width="15" precision="11"
unit="deg"><!-- ucd="POS_EQ_RA_MAIN" -->
<DESCRIPTION>Right ascension (ICRS) at Ep=2016.0 (ra)</DESCRIPTION>
</FIELD>
<FIELD name="DE_ICRS" ucd="pos.eq.dec;meta.main" ref="H_2016.000" datatype="double" width="15" precision="11"
unit="deg"><!-- ucd="POS_EQ_DEC_MAIN" -->
<DESCRIPTION>Declination (ICRS) at Ep=2016.0 (dec)</DESCRIPTION>
</FIELD>
<FIELD name="Source" ucd="meta.id;meta.main" datatype="long" width="19"><!-- ucd="ID_MAIN" -->
<DESCRIPTION>Unique source identifier (unique within a particular Data Release) (source_id)</DESCRIPTION>
</FIELD>
<FIELD name="e_RA_ICRS" ucd="stat.error;pos.eq.ra" datatype="double" width="7" precision="4" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>Standard error of right ascension (ra_error)</DESCRIPTION>
</FIELD>
<FIELD name="e_DE_ICRS" ucd="stat.error;pos.eq.dec" datatype="double" width="7" precision="4" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>Standard error of declination (dec_error)</DESCRIPTION>
</FIELD>
<FIELD name="Plx" ucd="pos.parallax.trig" datatype="double" width="9" precision="4" unit="mas"><!-- ucd="POS_PARLX_TRIG" -->
<DESCRIPTION>? Parallax (parallax)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Plx" ucd="stat.error;pos.parallax.trig" datatype="float" width="7" precision="4" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>? Standard error of parallax (parallax_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="PM" ucd="pos.pm;pos.eq" datatype="double" width="9" precision="3" unit="mas/yr"><!-- ucd="POS_EQ_PM" -->
<DESCRIPTION>? Total proper motion (pm)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmRA" ucd="pos.pm;pos.eq.ra" ref="H_2016.000" datatype="double" width="9" precision="3" unit="mas/yr"><!-- ucd="POS_EQ_PMRA" -->
<DESCRIPTION>? Proper motion in right ascension direction, pmRA*cosDE (pmra)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_pmRA" ucd="stat.error;pos.pm;pos.eq.ra" datatype="float" width="6" precision="3" unit="mas/yr"><!-- ucd="ERROR" -->
<DESCRIPTION>? Standard error of proper motion in right ascension direction (pmra_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmDE" ucd="pos.pm;pos.eq.dec" ref="H_2016.000" datatype="double" width="9" precision="3" unit="mas/yr"><!-- ucd="POS_EQ_PMDEC" -->
<DESCRIPTION>? Proper motion in declination direction (pmdec)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_pmDE" ucd="stat.error;pos.pm;pos.eq.dec" datatype="float" width="6" precision="3" unit="mas/yr"><!-- ucd="ERROR" -->
<DESCRIPTION>? Standard error of proper motion in declination direction (pmdec_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RADEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1] Correlation between right ascension and declination (ra_dec_corr)</DESCRIPTION>
</FIELD>
<FIELD name="RAPlxcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between right ascension and parallax (ra_parallax_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RApmRAcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between right ascension and proper motion in right ascension (ra_pmra_corr)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RApmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between right ascension and proper motion in declination (ra_pmdec_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="DEPlxcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between declination and parallax (dec_parallax_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="DEpmRAcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between declination and proper motion in right ascension (dec_pmra_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="DEpmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between declination and proper motion in declination (dec_pmdec_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="PlxpmRAcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between parallax and proper motion in right ascension (parallax_pmra_corr)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="PlxpmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between parallax and proper motion in declination (parallax_pmdec_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmRApmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between proper motion in right ascension and proper motion in declination
(pmra_pmdec_corr)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RV" ucd="spect.dopplerVeloc.opt;em.opt.I" datatype="double" width="7" precision="2" unit="km/s"><!-- ucd="VELOC_BARYCENTER" -->
<DESCRIPTION>? Radial velocity (radial_velocity)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_RV" ucd="stat.error;spect.dopplerVeloc.opt;em.opt.I" datatype="float" width="5" precision="2"
unit="km/s"><!-- ucd="ERROR" -->
<DESCRIPTION>? Radial velocity error (radial_velocity_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="Vbroad" ucd="spect.dopplerVeloc.opt;em.opt.I" datatype="double" width="8" precision="4" unit="km/s"><!-- ucd="SPECT_LINE_BROADENING" -->
<DESCRIPTION>? Spectral line broadening parameter (vbroad)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="GRVSmag" ucd="phot.mag;em.opt" datatype="double" width="9" precision="6" unit="mag"><!-- ucd="PHOT_MAG_OPTICAL" -->
<DESCRIPTION>? Integrated Grvs magnitude (grvs_mag)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="QSO" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of additional information in the QSO candidates table
(in_qso_candidates)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${QSO}%7d%5cvMore%7b-source=I/356/qsocand%5c%26Source=${Source}%7d%7b${QSO}%7d%5celse%7b${QSO}%7d%5cfi" />
</FIELD>
<FIELD name="Gal" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of additional information in the galaxy candidates table
(in_galaxy_candidates)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${Gal}%7d%5cvMore%7b-source=I/356/galcand%5c%26Source=${Source}%7d%7b${Gal}%7d%5celse%7b${Gal}%7d%5cfi" />
</FIELD>
<FIELD name="NSS" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/7] Flag indicating the availability of additional information in the various Non-Single Star tables
(non_single_star)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${NSS}%7d%5cvMore%7b-source=I/357/tb%2a%5c%26Source=${Source}%7d%7b${NSS}%7d%5celse%7b${NSS}%7d%5cfi" />
</FIELD>
<FIELD name="XPcont" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of mean BP/RP spectrum in continuous representation for this source
(has_xp_continuous)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${XPcont}%7d%5cvMore%7b-source=I/355/xpco%2a%5c%26Source=${Source}%7d%7b${XPcont}%7d%5celse%7b${XPcont}%7d%5cfi" />
</FIELD>
<FIELD name="XPsamp" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of mean BP/RP spectrum in sampled form for this source
(has_xp_sampled)
</DESCRIPTION>
<LINK content-type="timeserie/votable"
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${XPsamp}%7d%5cvizContent%7bspectrum%7d%5cvplotTS%7bI/355%7d%7b.graph_sql_xpsamp%7d%7bPos=${RA_ICRS}${DE_ICRS}%26Source=${Source}%7d%7b${XPsamp}%7d%5celse%7b${XPsamp}%7d%5cfi" />
</FIELD>
<FIELD name="RVS" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of mean RVS spectrum for this source (has_rvs)</DESCRIPTION>
<LINK content-type="timeserie/votable"
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${RVS}%7d%5cvizContent%7btimeSerie%7d%5cvplotTS%7bI/355%7d%7b.graph_sql_rvs%7d%7bPos=${RA_ICRS}${DE_ICRS}%26Source=${Source}%7d%7b${RVS}%7d%5celse%7b${RVS}%7d%5cfi" />
</FIELD>
<FIELD name="EpochPh" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of epoch photometry for this source (has_epoch_photometry)
</DESCRIPTION>
<LINK content-type="timeserie/votable"
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${EpochPh}%7d%5cvizContent%7btimeSerie%7d%5cvplotTS%7bI/355%7d%7b.graph_sql_epphot%7d%7bPos=${RA_ICRS}${DE_ICRS}%26Source=${Source}%7d%7b${EpochPh}%7d%5celse%7b${EpochPh}%7d%5cfi" />
</FIELD>
<FIELD name="EpochRV" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of epoch radial velocity for this source (has_epoch_rv)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${EpochRV}%7d%5cvMore%7b-source=I/358/veprv%5c%26Source=${Source}%7d%7b${EpochRV}%7d%5celse%7b${EpochRV}%7d%5cfi" />
</FIELD>
<FIELD name="MCMCGSP" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of GSP-Phot MCMC samples for this source (has_mcmc_gspphot)
</DESCRIPTION>
</FIELD>
<FIELD name="MCMCMSC" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of MSC MCMC samples for this source (has_mcmc_msc)</DESCRIPTION>
</FIELD>
<FIELD name="And" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating that the source is present in the Gaia Andromeda Photometric Survey (GAPS)
(in_andromeda_survey)
</DESCRIPTION>
</FIELD>
<FIELD name="Teff" ucd="phys.temperature.effective" datatype="double" width="7" precision="1" unit="K"><!-- ucd="PHYS_TEMP_EFFEC" -->
<DESCRIPTION>? Effective temperature from GSP-Phot Aeneas best library using BP/RP spectra (teff_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="logg" ucd="phys.gravity" datatype="double" width="7" precision="4" unit="log(cm.s**-2)"><!-- ucd="PHYS_GRAVITY_SURFACE" -->
<DESCRIPTION>? Surface gravity from GSP-Phot Aeneas best library using BP/RP spectra (logg_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="[Fe/H]" ID="__Fe_H_" ucd="phys.abund.Z" datatype="double" width="7" precision="4" unit=""><!-- ucd="PHYS_ABUND_FE/H" -->
<DESCRIPTION>? Iron abundance from GSP-Phot Aeneas best library using BP/RP spectra (mh_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="Dist" ucd="pos.distance;pos.eq" datatype="double" width="10" precision="4" unit="pc"><!-- ucd="PHYS_DISTANCE_TRUE" -->
<DESCRIPTION>? Distance from GSP-Phot Aeneas best library using BP/RP spectra (distance_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="A0" ucd="phys.absorption;em.opt" datatype="double" width="7" precision="4" unit="mag"><!-- ucd="PHOT_EXTINCTION_TOTAL" -->
<DESCRIPTION>? Monochromatic extinction A_0 at 547.7nm from GSP-Phot Aeneas best library using BP/RP spectra
(azero_gspphot)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="HIP" ucd="meta.number;phys.atmol.number" datatype="int" width="6"><!-- ucd="ID_NUMBER" -->
<DESCRIPTION>? HIP cross-id number, van Leeuwen, Cat. I/311 (hip_original_ext_source_id)</DESCRIPTION>
<VALUES null="-2147483648" />
</FIELD>
<FIELD name="PS1" ucd="meta.id.cross" datatype="long" width="18"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? PS1 cross-id name, Chambers et al., Cat. II/349 (ps1_original_ext_source_id)</DESCRIPTION>
<VALUES null="-9223372036854775808" />
</FIELD>
<FIELD name="SDSS13" ucd="meta.id.cross" datatype="long" width="19"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? SDSS name, Albareti et al., 2017ApJS..233...25A (sdss13_ext_source_id)</DESCRIPTION>
<VALUES null="-9223372036854775808" />
</FIELD>
<FIELD name="SKYM2" ucd="meta.id.cross" datatype="int" width="9"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? SkyMapperDR2 cross-id name, Onken et al., 2019PASA...36...33O (skym2_original_ext_source_id)
</DESCRIPTION>
<VALUES null="-2147483648" />
</FIELD>
<FIELD name="TYC2" ucd="meta.id.cross" datatype="char" arraysize="12*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>Tycho-2 cross-id name, Hog et al., Cat. I/259 (tyc2_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="URAT1" ucd="meta.id.cross" datatype="char" arraysize="15*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>URAT1 name, Zacharias et al., Cat. I/329 (urat1_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="AllWISE" ucd="meta.id.cross" datatype="char" arraysize="19*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>ALLWISE cross-id name, Cutri et al., Cat. II/328 (allwise_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="APASS9" ucd="meta.id.cross" datatype="int" width="8"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? APASS9 identification, Henden et al., Cat. II/336 (apass9_original_ext_source_id)</DESCRIPTION>
<VALUES null="-2147483648" />
</FIELD>
<FIELD name="GSC23" ucd="meta.id.cross" datatype="char" arraysize="10*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>GSC2.3 cross-id name, Lasker et al., Cat. I/305 (gsc23_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="RAVE5" ucd="meta.id.cross" datatype="char" arraysize="16*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>RAVE DR5 cross-id name, Kunder et al., Cat. III/279 (rave5_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="2MASS" ucd="meta.id.cross" ID="_2MASS" datatype="char" arraysize="17*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>2MASS cross-id name, Cutri et al., Cat. II/246 (twomass_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="RAVE6" ucd="meta.id.cross" datatype="char" arraysize="21*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>RAVE DR6 cross-id name, Steinmetz et al., Cat. III/283 (rave6_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="RAJ2000" ucd="pos.eq.ra" datatype="double" width="15" precision="11" unit="deg"><!-- ucd="POS_EQ_RA_OTHER" -->
<DESCRIPTION>Barycentric right ascension (ICRS) at Ep=2000.0 (added by CDS) (ra2000)</DESCRIPTION>
</FIELD>
<FIELD name="DEJ2000" ucd="pos.eq.dec" datatype="double" width="15" precision="11" unit="deg"><!-- ucd="POS_EQ_DEC_OTHER" -->
<DESCRIPTION>Barycentric declination (ICRS) at Ep=2000.0 (added by CDS) (dec2000)</DESCRIPTION>
</FIELD>
<DATA>
<TABLEDATA>
<TR>
<TD>307.7911702004070</TD>
<TD>+20.4311044212247</TD>
<TD>Gaia DR3 1816065554621487360</TD>
<TD>307.79115807079</TD>
<TD>+20.43108005561</TD>
<TD>1816065554621487360</TD>
<TD>0.0511</TD>
<TD>0.0477</TD>
<TD>0.4319</TD>
<TD>0.0691</TD>
<TD>6.049</TD>
<TD>-2.557</TD>
<TD>0.064</TD>
<TD>-5.482</TD>
<TD>0.067</TD>
<TD>0.1212</TD>
<TD>0.1337</TD>
<TD>-0.4109</TD>
<TD>-0.0072</TD>
<TD>0.0069</TD>
<TD>-0.0085</TD>
<TD>-0.2983</TD>
<TD>-0.2603</TD>
<TD>-0.0251</TD>
<TD>0.2688</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>1</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>1</TD>
<TD>1</TD>
<TD>0</TD>
<TD>4696.2</TD>
<TD>4.1928</TD>
<TD>-0.7942</TD>
<TD>2428.5532</TD>
<TD>0.0380</TD>
<TD></TD>
<TD>132513077911637877</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>URAT1-553569515</TD>
<TD></TD>
<TD></TD>
<TD>N2PD017546</TD>
<TD></TD>
<TD>20310987+2025519</TD>
<TD></TD>
<TD>307.79117020041</TD>
<TD>20.43110442122</TD>
</TR>
<TR>
<TD>307.7958370989668</TD>
<TD>+20.4325479549513</TD>
<TD>Gaia DR3 1816065558924798592</TD>
<TD>307.79582391363</TD>
<TD>+20.43253083107</TD>
<TD>1816065558924798592</TD>
<TD>0.3203</TD>
<TD>0.2898</TD>
<TD>-0.5553</TD>
<TD>0.4195</TD>
<TD>4.751</TD>
<TD>-2.780</TD>
<TD>0.395</TD>
<TD>-3.853</TD>
<TD>0.426</TD>
<TD>0.3740</TD>
<TD>0.1981</TD>
<TD>-0.3253</TD>
<TD>-0.0486</TD>
<TD>0.0632</TD>
<TD>-0.0608</TD>
<TD>-0.1303</TD>
<TD>-0.2989</TD>
<TD>-0.0876</TD>
<TD>0.4716</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>132513077957999584</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>N2PD017483</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.79583709897</TD>
<TD>20.43254795495</TD>
</TR>
<TR>
<TD>307.7973736604703</TD>
<TD>+20.4355882715396</TD>
<TD>Gaia DR3 1816065558920966528</TD>
<TD>307.79737366047</TD>
<TD>+20.43558827154</TD>
<TD>1816065558920966528</TD>
<TD>6.0643</TD>
<TD>2.1063</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0.7514</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>132523077973693273</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.79737366047</TD>
<TD>20.43558827154</TD>
</TR>
<TR>
<TD>307.7885745651585</TD>
<TD>+20.4301331822264</TD>
<TD>Gaia DR3 1816065558924801792</TD>
<TD>307.78856137441</TD>
<TD>+20.43011713943</TD>
<TD>1816065558924801792</TD>
<TD>0.1196</TD>
<TD>0.1127</TD>
<TD>0.0910</TD>
<TD>0.1671</TD>
<TD>4.557</TD>
<TD>-2.781</TD>
<TD>0.153</TD>
<TD>-3.610</TD>
<TD>0.165</TD>
<TD>0.1448</TD>
<TD>0.1580</TD>
<TD>-0.3400</TD>
<TD>0.0468</TD>
<TD>0.0149</TD>
<TD>0.0299</TD>
<TD>-0.2083</TD>
<TD>-0.2178</TD>
<TD>0.0615</TD>
<TD>0.3266</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>1</TD>
<TD>0</TD>
<TD>0</TD>
<TD>5071.5</TD>
<TD>4.6975</TD>
<TD>-0.7109</TD>
<TD>2923.6248</TD>
<TD>0.0090</TD>
<TD></TD>
<TD>132513077885716709</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>URAT1-553569502</TD>
<TD></TD>
<TD></TD>
<TD>N2PD017266</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.78857456516</TD>
<TD>20.43013318223</TD>
</TR>
<TR>
<TD>307.7897816527446</TD>
<TD>+20.4315208963567</TD>
<TD>Gaia DR3 1816065554622254464</TD>
<TD>307.78977228741</TD>
<TD>+20.43150439876</TD>
<TD>1816065554622254464</TD>
<TD>0.3688</TD>
<TD>0.3801</TD>
<TD>-0.6243</TD>
<TD>0.5059</TD>
<TD>4.205</TD>
<TD>-1.975</TD>
<TD>0.444</TD>
<TD>-3.712</TD>
<TD>0.498</TD>
<TD>0.2553</TD>
<TD>0.0889</TD>
<TD>-0.3890</TD>
<TD>-0.0365</TD>
<TD>-0.0773</TD>
<TD>-0.0307</TD>
<TD>-0.2998</TD>
<TD>-0.2452</TD>
<TD>0.0025</TD>
<TD>0.3249</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>132513077897708356</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>N2PD096440</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.78978165274</TD>
<TD>20.43152089636</TD>
</TR>
</TABLEDATA>
</DATA>
</TABLE>
<INFO name="matches" value="5">matching records</INFO>
<INFO name="Warning" value="No center provided++++" />
<INFO name="Warning" value="increase the precision for computed column 14" />
<INFO name="Warning" value="increase the precision for computed column 14" />
<INFO name="Warning" value="truncated result (maxtup=5)" />
<INFO name="QUERY_STATUS" value="OVERFLOW"> value="truncated result (maxtup=5)"</INFO>
</RESOURCE>
</VOTABLE>
<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ivoa.net/xml/VOTable/v1.3"
xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/v1.3">
<DESCRIPTION>
VizieR Astronomical Server cds:8082
Date: 2025-04-19T10:08:58 [V7.5]
Explanations and Statistics of UCDs: See LINK below
In case of problem, please report to: cds-question@unistra.fr
</DESCRIPTION>
<!-- VOTable description at http://www.ivoa.net/Documents/latest/VOT.html -->
<INFO name="service_protocol" value="ivo://ivoa.net/std/ConeSearch/v1.03"> IVOID of the protocol through which the data was retrieved</INFO>
<INFO name="request_date" value="2025-04-19T10:08:58"> Query execution date</INFO>
<INFO name="request" value="https://cds/local/viz-bin/mivotconesearch/VIII/6/maps?RA=0.6405&amp;DEC=-8.221&amp;SR=1"> Full request URL</INFO>
<INFO name="contact" value="cds-question@unistra.fr"> Email or URL to contact publisher</INFO>
<INFO name="server_software" value="7.5"> Software version</INFO>
<INFO name="publisher" value="CDS"> Data centre that produced the VOTable</INFO>
<!--
Execution Reports
-->
<INFO name="MaxTuples" value="50000" />
<INFO ID="Target" name="-c" value="000.640500-08.221000,rd=1.">
Constraint -out.meta=dhuM</INFO>
<RESOURCE ID="yCat_8006" name="VIII/6" type="results">
<DESCRIPTION>1400-MHz Sky Survey, Maps Covering Dec -5 to +82 (Condon+ 1985-86)</DESCRIPTION> <INFO name="ivoid" value="ivo://cds.vizier/viii/6"> IVOID of underlying data collection </INFO>
<INFO name="creator" value="Condon J.J."> First author or institution </INFO>
<INFO name="cites" value="bibcode:1985AJ.....90.2540C"> Article or Data origin sources </INFO>
<INFO name="editor" value="Astronomical Journal (AAS)"> Editor name (article) </INFO>
<INFO name="original_date" value="1985"> Year of the article publication </INFO>
<INFO name="reference_url" value="https://cdsarc.cds.unistra.fr/viz-bin/cat/VIII/6"> Dataset landing page </INFO>
<INFO name="publication_date" value="2018-01-31"> Date of first publication in the data centre </INFO>
<INFO name="rights_uri" value="https://cds.unistra.fr/vizier-org/licences_vizier.html"> Licence URI </INFO>
<COOSYS ID="B1950" system="eq_FK4" equinox="B1950"/>
<TABLE ID="VIII_6_maps" name="VIII/6/maps">
<DESCRIPTION>List of the map centers</DESCRIPTION>
<!-- Definitions of GROUPs and FIELDs -->
<!--The precision of the computed positions has been increased compared to the original positions-->
<FIELD name="_r" ucd="pos.angDistance" datatype="double" width="4" precision="2" unit=""><!-- ucd="POS_ANG_DIST_GENERAL" -->
<DESCRIPTION>Distance from center (000.0-08.5)[FK4/B1950]</DESCRIPTION>
</FIELD>
<FIELD name="recno" ucd="ID_MAIN" datatype="char" arraysize="8*"><!-- ucd="meta.record" -->
<DESCRIPTION>Record number assigned by the VizieR team. Should Not be used for identification. [datatype=int]</DESCRIPTION>
</FIELD>
<FIELD name="ObsDate" ucd="time.epoch;obs" datatype="char" arraysize="10" unit="'Y:M:D'"><!-- ucd="TIME_DATE" -->
<DESCRIPTION>Observation date</DESCRIPTION>
<VALUES null=" " />
</FIELD>
<FIELD name="RAB1950" ucd="POS_EQ_RA_MAIN" ref="B1950" datatype="double" width="5" precision="1" unit="deg"><!-- ucd="pos.eq.ra;meta.main" -->
<DESCRIPTION>Right Ascension (B1950) of map center</DESCRIPTION>
</FIELD>
<FIELD name="DEB1950" ucd="POS_EQ_DEC_MAIN" ref="B1950" datatype="double" width="5" precision="1" unit="deg"><!-- ucd="pos.eq.dec;meta.main" -->
<DESCRIPTION>Declination (B1950) of map center</DESCRIPTION>
</FIELD>
<FIELD name="FITSfile" ucd="meta.id;meta.fits" datatype="char" arraysize="8"><!-- ucd="ID_FILE" -->
<DESCRIPTION>Name of FITS file in "maps" subdirectory</DESCRIPTION>
<!-- Name of FITS file in "\begin{tex}\vFile{VIII/6/./maps}{maps}\end{tex}" subdirectory -->
</FIELD>
<FIELD name="AssocData" ucd="meta.bib.page" datatype="char" arraysize="4"><!-- ucd="DATA_LINK" -->
<DESCRIPTION>Associated Data web page</DESCRIPTION>
</FIELD>
<FIELD name="FITS" ucd="meta.ref.url" datatype="char" arraysize="4"><!-- ucd="DATA_LINK" -->
<DESCRIPTION>Downbload the FITS file</DESCRIPTION>
</FIELD>
<FIELD name="_RA.icrs" ucd="pos.eq.ra" datatype="double" width="8" precision="4" unit="deg"><!-- ucd="POS_EQ_RA_OTHER" -->
<DESCRIPTION>Right ascension (ICRS) (computed by VizieR, not part of the original data)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="_DE.icrs" ucd="pos.eq.dec" datatype="double" width="8" precision="4" unit="deg"><!-- ucd="POS_EQ_DEC_OTHER" -->
<DESCRIPTION>Declination (ICRS) (computed by VizieR, not part of the original data)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<DATA><TABLEDATA>
<TR><TD>0.00</TD><TD>1</TD><TD>1983-10-06</TD><TD>000.0</TD><TD>-08.5</TD><TD>f001.fit</TD><TD>fits</TD><TD>FITS</TD><TD>000.6405</TD><TD>-08.2216</TD></TR>
</TABLEDATA></DATA>
</TABLE>
<INFO name="matches" value="1">matching records</INFO>
</RESOURCE>
</VOTABLE>

Sorry, the diff of this file is too big to display

<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ivoa.net/xml/VOTable/v1.3"
xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/v1.3">
<DESCRIPTION>
VizieR Astronomical Server vizier.cds.unistra.fr
Date: 2024-03-21T15:16:08 [V7.33.3]
Explanations and Statistics of
UCDs: See LINK below
In case of problem, please report to: cds-question@unistra.fr
</DESCRIPTION>
<!-- VOTable description at http://www.ivoa.net/Documents/latest/VOT.html -->
<INFO name="service_protocol" value="ASU"> IVOID of the protocol through which the data was retrieved</INFO>
<INFO name="request_date" value="2024-03-21T15:16:08"> Query execution date</INFO>
<INFO name="request"
value="https://vizier.cds.unistra.fr/viz-bin/votable?-oc.form=dec&amp;-out.max=5&amp;-out.add=_r&amp;-out.add=_RAJ,_DEJ&amp;-sort=_r&amp;-c.eq=J2000&amp;-c.r= 2&amp;-c.u=arcmin&amp;-c.geom=r&amp;-source=I/355/gaiadr3&amp;-order=I&amp;-out.orig=standard&amp;-out=DR3Name&amp;-out=RA_ICRS&amp;-out=DE_ICRS&amp;-out=Source&amp;-out=e_RA_ICRS&amp;-out=e_DE_ICRS&amp;-out=Plx&amp;-out=e_Plx&amp;-out=PM&amp;-out=pmRA&amp;-out=e_pmRA&amp;-out=pmDE&amp;-out=e_pmDE&amp;-out=RADEcor&amp;-out=RAPlxcor&amp;-out=RApmRAcor&amp;-out=RApmDEcor&amp;-out=DEPlxcor&amp;-out=DEpmRAcor&amp;-out=DEpmDEcor&amp;-out=PlxpmRAcor&amp;-out=PlxpmDEcor&amp;-out=pmRApmDEcor&amp;-out=RV&amp;-out=e_RV&amp;-out=Vbroad&amp;-out=GRVSmag&amp;-out=QSO&amp;-out=Gal&amp;-out=NSS&amp;-out=XPcont&amp;-out=XPsamp&amp;-out=RVS&amp;-out=EpochPh&amp;-out=EpochRV&amp;-out=MCMCGSP&amp;-out=MCMCMSC&amp;-out=And&amp;-out=Teff&amp;-out=logg&amp;-out=[Fe/H]&amp;-out=Dist&amp;-out=A0&amp;-out=HIP&amp;-out=PS1&amp;-out=SDSS13&amp;-out=SKYM2&amp;-out=TYC2&amp;-out=URAT1&amp;-out=AllWISE&amp;-out=APASS9&amp;-out=GSC23&amp;-out=RAVE5&amp;-out=2MASS&amp;-out=RAVE6&amp;-out=RAJ2000&amp;-out=DEJ2000&amp;"> Full request URL (POST)</INFO>
<INFO name="contact" value="cds-question@unistra.fr"> Email or URL to contact publisher</INFO>
<INFO name="server_software" value="7.33.3"> Software version</INFO>
<INFO name="publisher" value="CDS"> Data centre that produced the VOTable</INFO>
<!-- Execution Reports -->
<RESOURCE ID="yCat_1355" name="I/355">
<DESCRIPTION>Gaia DR3 Part 1. Main source (Gaia Collaboration, 2022)</DESCRIPTION>
<INFO name="ivoid" value="ivo://cds.vizier/i/355"> IVOID of underlying data collection </INFO>
<INFO name="creator" value="Gaia collaboration"> First author or institution </INFO>
<INFO name="cites" value="bibcode:2022yCat.1355....0G"> Bibcode of the dataset </INFO>
<INFO name="original_date" value="2022"> Year of the article publication </INFO>
<INFO name="reference_url" value="https://cdsarc.cds.unistra.fr/viz-bin/cat/I/355"> Dataset landing page </INFO>
<INFO name="citation" value="doi:10.26093/cds/vizier.1355"> Dataset identifier that can be used for citation </INFO>
<INFO name="publication_date" value="2024-01-24"> Date of first publication in the data centre </INFO>
<INFO name="rights_uri" value="https://cds.unistra.fr/vizier-org/licences_vizier.html"> Licence URI </INFO>
<COOSYS ID="H_2016.000" system="ICRS" epoch="2016.000" />
<COOSYS ID="J2000" system="eq_FK5" equinox="J2000" />
<TABLE ID="I_355_gaiadr3" name="I/355/gaiadr3">
<DESCRIPTION>Gaia DR3 source catalog (1811709771 sources)</DESCRIPTION>
<!-- Gaia data release 3 (Gaia DR3). (\originalcolumnnames)\vizContent{timeSerie}\vizContent{spectrum} -->
<!-- Definitions of GROUPs and FIELDs -->
<FIELD name="_RAJ2000" ucd="pos.eq.ra" ref="J2000" datatype="double" width="17" precision="13" unit="deg"><!-- ucd="POS_EQ_RA" -->
<DESCRIPTION>Right ascension (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account </DESCRIPTION>
</FIELD>
<FIELD name="_DEJ2000" ucd="pos.eq.dec" ref="J2000" datatype="double" width="17" precision="13" unit="deg"><!-- ucd="POS_EQ_DEC" -->
<DESCRIPTION>Declination (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account </DESCRIPTION>
</FIELD>
<FIELD name="DR3Name" ucd="meta.id" datatype="char" arraysize="28*"><!-- ucd="ID_ALTERNATIVE" -->
<DESCRIPTION>Unique source designation (unique across all Data Releases) (designation)</DESCRIPTION>
</FIELD>
<FIELD name="RA_ICRS" ucd="pos.eq.ra;meta.main" ref="H_2016.000" datatype="double" width="15" precision="11"
unit="deg"><!-- ucd="POS_EQ_RA_MAIN" -->
<DESCRIPTION>Right ascension (ICRS) at Ep=2016.0 (ra)</DESCRIPTION>
</FIELD>
<FIELD name="DE_ICRS" ucd="pos.eq.dec;meta.main" ref="H_2016.000" datatype="double" width="15" precision="11"
unit="deg"><!-- ucd="POS_EQ_DEC_MAIN" -->
<DESCRIPTION>Declination (ICRS) at Ep=2016.0 (dec)</DESCRIPTION>
</FIELD>
<FIELD name="Source" ucd="meta.id;meta.main" datatype="long" width="19"><!-- ucd="ID_MAIN" -->
<DESCRIPTION>Unique source identifier (unique within a particular Data Release) (source_id)</DESCRIPTION>
</FIELD>
<FIELD name="e_RA_ICRS" ucd="stat.error;pos.eq.ra" datatype="double" width="7" precision="4" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>Standard error of right ascension (ra_error)</DESCRIPTION>
</FIELD>
<FIELD name="e_DE_ICRS" ucd="stat.error;pos.eq.dec" datatype="double" width="7" precision="4" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>Standard error of declination (dec_error)</DESCRIPTION>
</FIELD>
<FIELD name="Plx" ucd="pos.parallax.trig" datatype="double" width="9" precision="4" unit="mas"><!-- ucd="POS_PARLX_TRIG" -->
<DESCRIPTION>? Parallax (parallax)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Plx" ucd="stat.error;pos.parallax.trig" datatype="float" width="7" precision="4" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>? Standard error of parallax (parallax_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="PM" ucd="pos.pm;pos.eq" datatype="double" width="9" precision="3" unit="mas/yr"><!-- ucd="POS_EQ_PM" -->
<DESCRIPTION>? Total proper motion (pm)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmRA" ucd="pos.pm;pos.eq.ra" ref="H_2016.000" datatype="double" width="9" precision="3" unit="mas/yr"><!-- ucd="POS_EQ_PMRA" -->
<DESCRIPTION>? Proper motion in right ascension direction, pmRA*cosDE (pmra)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_pmRA" ucd="stat.error;pos.pm;pos.eq.ra" datatype="float" width="6" precision="3" unit="mas/yr"><!-- ucd="ERROR" -->
<DESCRIPTION>? Standard error of proper motion in right ascension direction (pmra_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmDE" ucd="pos.pm;pos.eq.dec" ref="H_2016.000" datatype="double" width="9" precision="3" unit="mas/yr"><!-- ucd="POS_EQ_PMDEC" -->
<DESCRIPTION>? Proper motion in declination direction (pmdec)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_pmDE" ucd="stat.error;pos.pm;pos.eq.dec" datatype="float" width="6" precision="3" unit="mas/yr"><!-- ucd="ERROR" -->
<DESCRIPTION>? Standard error of proper motion in declination direction (pmdec_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RADEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1] Correlation between right ascension and declination (ra_dec_corr)</DESCRIPTION>
</FIELD>
<FIELD name="RAPlxcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between right ascension and parallax (ra_parallax_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RApmRAcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between right ascension and proper motion in right ascension (ra_pmra_corr)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RApmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between right ascension and proper motion in declination (ra_pmdec_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="DEPlxcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between declination and parallax (dec_parallax_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="DEpmRAcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between declination and proper motion in right ascension (dec_pmra_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="DEpmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between declination and proper motion in declination (dec_pmdec_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="PlxpmRAcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between parallax and proper motion in right ascension (parallax_pmra_corr)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="PlxpmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between parallax and proper motion in declination (parallax_pmdec_corr)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmRApmDEcor" ucd="stat.correlation" datatype="double" width="7" precision="4"><!-- ucd="STAT_CORRELATION" -->
<DESCRIPTION>[-1/1]? Correlation between proper motion in right ascension and proper motion in declination
(pmra_pmdec_corr)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="RV" ucd="spect.dopplerVeloc.opt;em.opt.I" datatype="double" width="7" precision="2" unit="km/s"><!-- ucd="VELOC_BARYCENTER" -->
<DESCRIPTION>? Radial velocity (radial_velocity)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_RV" ucd="stat.error;spect.dopplerVeloc.opt;em.opt.I" datatype="float" width="5" precision="2"
unit="km/s"><!-- ucd="ERROR" -->
<DESCRIPTION>? Radial velocity error (radial_velocity_error)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="Vbroad" ucd="spect.dopplerVeloc.opt;em.opt.I" datatype="double" width="8" precision="4" unit="km/s"><!-- ucd="SPECT_LINE_BROADENING" -->
<DESCRIPTION>? Spectral line broadening parameter (vbroad)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="GRVSmag" ucd="phot.mag;em.opt" datatype="double" width="9" precision="6" unit="mag"><!-- ucd="PHOT_MAG_OPTICAL" -->
<DESCRIPTION>? Integrated Grvs magnitude (grvs_mag)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="QSO" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of additional information in the QSO candidates table
(in_qso_candidates)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${QSO}%7d%5cvMore%7b-source=I/356/qsocand%5c%26Source=${Source}%7d%7b${QSO}%7d%5celse%7b${QSO}%7d%5cfi" />
</FIELD>
<FIELD name="Gal" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of additional information in the galaxy candidates table
(in_galaxy_candidates)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${Gal}%7d%5cvMore%7b-source=I/356/galcand%5c%26Source=${Source}%7d%7b${Gal}%7d%5celse%7b${Gal}%7d%5cfi" />
</FIELD>
<FIELD name="NSS" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/7] Flag indicating the availability of additional information in the various Non-Single Star tables
(non_single_star)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${NSS}%7d%5cvMore%7b-source=I/357/tb%2a%5c%26Source=${Source}%7d%7b${NSS}%7d%5celse%7b${NSS}%7d%5cfi" />
</FIELD>
<FIELD name="XPcont" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of mean BP/RP spectrum in continuous representation for this source
(has_xp_continuous)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${XPcont}%7d%5cvMore%7b-source=I/355/xpco%2a%5c%26Source=${Source}%7d%7b${XPcont}%7d%5celse%7b${XPcont}%7d%5cfi" />
</FIELD>
<FIELD name="XPsamp" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of mean BP/RP spectrum in sampled form for this source
(has_xp_sampled)
</DESCRIPTION>
<LINK content-type="timeserie/votable"
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${XPsamp}%7d%5cvizContent%7bspectrum%7d%5cvplotTS%7bI/355%7d%7b.graph_sql_xpsamp%7d%7bPos=${RA_ICRS}${DE_ICRS}%26Source=${Source}%7d%7b${XPsamp}%7d%5celse%7b${XPsamp}%7d%5cfi" />
</FIELD>
<FIELD name="RVS" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of mean RVS spectrum for this source (has_rvs)</DESCRIPTION>
<LINK content-type="timeserie/votable"
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${RVS}%7d%5cvizContent%7btimeSerie%7d%5cvplotTS%7bI/355%7d%7b.graph_sql_rvs%7d%7bPos=${RA_ICRS}${DE_ICRS}%26Source=${Source}%7d%7b${RVS}%7d%5celse%7b${RVS}%7d%5cfi" />
</FIELD>
<FIELD name="EpochPh" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of epoch photometry for this source (has_epoch_photometry)
</DESCRIPTION>
<LINK content-type="timeserie/votable"
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${EpochPh}%7d%5cvizContent%7btimeSerie%7d%5cvplotTS%7bI/355%7d%7b.graph_sql_epphot%7d%7bPos=${RA_ICRS}${DE_ICRS}%26Source=${Source}%7d%7b${EpochPh}%7d%5celse%7b${EpochPh}%7d%5cfi" />
</FIELD>
<FIELD name="EpochRV" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of epoch radial velocity for this source (has_epoch_rv)
</DESCRIPTION>
<LINK
href="http://vizier.cds.unistra.fr/viz-bin/nph-htx/A?%5cifmatch%7b1%7d%7b${EpochRV}%7d%5cvMore%7b-source=I/358/veprv%5c%26Source=${Source}%7d%7b${EpochRV}%7d%5celse%7b${EpochRV}%7d%5cfi" />
</FIELD>
<FIELD name="MCMCGSP" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of GSP-Phot MCMC samples for this source (has_mcmc_gspphot)
</DESCRIPTION>
</FIELD>
<FIELD name="MCMCMSC" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating the availability of MSC MCMC samples for this source (has_mcmc_msc)</DESCRIPTION>
</FIELD>
<FIELD name="And" ucd="meta.code.status" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[0/1] Flag indicating that the source is present in the Gaia Andromeda Photometric Survey (GAPS)
(in_andromeda_survey)
</DESCRIPTION>
</FIELD>
<FIELD name="Teff" ucd="phys.temperature.effective" datatype="double" width="7" precision="1" unit="K"><!-- ucd="PHYS_TEMP_EFFEC" -->
<DESCRIPTION>? Effective temperature from GSP-Phot Aeneas best library using BP/RP spectra (teff_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="logg" ucd="phys.gravity" datatype="double" width="7" precision="4" unit="log(cm.s**-2)"><!-- ucd="PHYS_GRAVITY_SURFACE" -->
<DESCRIPTION>? Surface gravity from GSP-Phot Aeneas best library using BP/RP spectra (logg_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="[Fe/H]" ID="__Fe_H_" ucd="phys.abund.Z" datatype="double" width="7" precision="4" unit=""><!-- ucd="PHYS_ABUND_FE/H" -->
<DESCRIPTION>? Iron abundance from GSP-Phot Aeneas best library using BP/RP spectra (mh_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="Dist" ucd="pos.distance;pos.eq" datatype="double" width="10" precision="4" unit="pc"><!-- ucd="PHYS_DISTANCE_TRUE" -->
<DESCRIPTION>? Distance from GSP-Phot Aeneas best library using BP/RP spectra (distance_gspphot)</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="A0" ucd="phys.absorption;em.opt" datatype="double" width="7" precision="4" unit="mag"><!-- ucd="PHOT_EXTINCTION_TOTAL" -->
<DESCRIPTION>? Monochromatic extinction A_0 at 547.7nm from GSP-Phot Aeneas best library using BP/RP spectra
(azero_gspphot)
</DESCRIPTION>
<VALUES null="NaN" />
</FIELD>
<FIELD name="HIP" ucd="meta.number;phys.atmol.number" datatype="int" width="6"><!-- ucd="ID_NUMBER" -->
<DESCRIPTION>? HIP cross-id number, van Leeuwen, Cat. I/311 (hip_original_ext_source_id)</DESCRIPTION>
<VALUES null="-2147483648" />
</FIELD>
<FIELD name="PS1" ucd="meta.id.cross" datatype="long" width="18"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? PS1 cross-id name, Chambers et al., Cat. II/349 (ps1_original_ext_source_id)</DESCRIPTION>
<VALUES null="-9223372036854775808" />
</FIELD>
<FIELD name="SDSS13" ucd="meta.id.cross" datatype="long" width="19"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? SDSS name, Albareti et al., 2017ApJS..233...25A (sdss13_ext_source_id)</DESCRIPTION>
<VALUES null="-9223372036854775808" />
</FIELD>
<FIELD name="SKYM2" ucd="meta.id.cross" datatype="int" width="9"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? SkyMapperDR2 cross-id name, Onken et al., 2019PASA...36...33O (skym2_original_ext_source_id)
</DESCRIPTION>
<VALUES null="-2147483648" />
</FIELD>
<FIELD name="TYC2" ucd="meta.id.cross" datatype="char" arraysize="12*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>Tycho-2 cross-id name, Hog et al., Cat. I/259 (tyc2_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="URAT1" ucd="meta.id.cross" datatype="char" arraysize="15*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>URAT1 name, Zacharias et al., Cat. I/329 (urat1_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="AllWISE" ucd="meta.id.cross" datatype="char" arraysize="19*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>ALLWISE cross-id name, Cutri et al., Cat. II/328 (allwise_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="APASS9" ucd="meta.id.cross" datatype="int" width="8"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>? APASS9 identification, Henden et al., Cat. II/336 (apass9_original_ext_source_id)</DESCRIPTION>
<VALUES null="-2147483648" />
</FIELD>
<FIELD name="GSC23" ucd="meta.id.cross" datatype="char" arraysize="10*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>GSC2.3 cross-id name, Lasker et al., Cat. I/305 (gsc23_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="RAVE5" ucd="meta.id.cross" datatype="char" arraysize="16*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>RAVE DR5 cross-id name, Kunder et al., Cat. III/279 (rave5_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="2MASS" ucd="meta.id.cross" ID="_2MASS" datatype="char" arraysize="17*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>2MASS cross-id name, Cutri et al., Cat. II/246 (twomass_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="RAVE6" ucd="meta.id.cross" datatype="char" arraysize="21*"><!-- ucd="ID_CROSSID" -->
<DESCRIPTION>RAVE DR6 cross-id name, Steinmetz et al., Cat. III/283 (rave6_original_ext_source_id)</DESCRIPTION>
</FIELD>
<FIELD name="RAJ2000" ucd="pos.eq.ra" datatype="double" width="15" precision="11" unit="deg"><!-- ucd="POS_EQ_RA_OTHER" -->
<DESCRIPTION>Barycentric right ascension (ICRS) at Ep=2000.0 (added by CDS) (ra2000)</DESCRIPTION>
</FIELD>
<FIELD name="DEJ2000" ucd="pos.eq.dec" datatype="double" width="15" precision="11" unit="deg"><!-- ucd="POS_EQ_DEC_OTHER" -->
<DESCRIPTION>Barycentric declination (ICRS) at Ep=2000.0 (added by CDS) (dec2000)</DESCRIPTION>
</FIELD>
<DATA>
<TABLEDATA>
<TR>
<TD>307.7911702004070</TD>
<TD>+20.4311044212247</TD>
<TD>Gaia DR3 1816065554621487360</TD>
<TD>307.79115807079</TD>
<TD>+20.43108005561</TD>
<TD>1816065554621487360</TD>
<TD>0.0511</TD>
<TD>0.0477</TD>
<TD>0.4319</TD>
<TD>0.0691</TD>
<TD>6.049</TD>
<TD>-2.557</TD>
<TD>0.064</TD>
<TD>-5.482</TD>
<TD>0.067</TD>
<TD>0.1212</TD>
<TD>0.1337</TD>
<TD>-0.4109</TD>
<TD>-0.0072</TD>
<TD>0.0069</TD>
<TD>-0.0085</TD>
<TD>-0.2983</TD>
<TD>-0.2603</TD>
<TD>-0.0251</TD>
<TD>0.2688</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>1</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>1</TD>
<TD>1</TD>
<TD>0</TD>
<TD>4696.2</TD>
<TD>4.1928</TD>
<TD>-0.7942</TD>
<TD>2428.5532</TD>
<TD>0.0380</TD>
<TD></TD>
<TD>132513077911637877</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>URAT1-553569515</TD>
<TD></TD>
<TD></TD>
<TD>N2PD017546</TD>
<TD></TD>
<TD>20310987+2025519</TD>
<TD></TD>
<TD>307.79117020041</TD>
<TD>20.43110442122</TD>
</TR>
<TR>
<TD>307.7958370989668</TD>
<TD>+20.4325479549513</TD>
<TD>Gaia DR3 1816065558924798592</TD>
<TD>307.79582391363</TD>
<TD>+20.43253083107</TD>
<TD>1816065558924798592</TD>
<TD>0.3203</TD>
<TD>0.2898</TD>
<TD>-0.5553</TD>
<TD>0.4195</TD>
<TD>4.751</TD>
<TD>-2.780</TD>
<TD>0.395</TD>
<TD>-3.853</TD>
<TD>0.426</TD>
<TD>0.3740</TD>
<TD>0.1981</TD>
<TD>-0.3253</TD>
<TD>-0.0486</TD>
<TD>0.0632</TD>
<TD>-0.0608</TD>
<TD>-0.1303</TD>
<TD>-0.2989</TD>
<TD>-0.0876</TD>
<TD>0.4716</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>132513077957999584</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>N2PD017483</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.79583709897</TD>
<TD>20.43254795495</TD>
</TR>
<TR>
<TD>307.7973736604703</TD>
<TD>+20.4355882715396</TD>
<TD>Gaia DR3 1816065558920966528</TD>
<TD>307.79737366047</TD>
<TD>+20.43558827154</TD>
<TD>1816065558920966528</TD>
<TD>6.0643</TD>
<TD>2.1063</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0.7514</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>132523077973693273</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.79737366047</TD>
<TD>20.43558827154</TD>
</TR>
<TR>
<TD>307.7885745651585</TD>
<TD>+20.4301331822264</TD>
<TD>Gaia DR3 1816065558924801792</TD>
<TD>307.78856137441</TD>
<TD>+20.43011713943</TD>
<TD>1816065558924801792</TD>
<TD>0.1196</TD>
<TD>0.1127</TD>
<TD>0.0910</TD>
<TD>0.1671</TD>
<TD>4.557</TD>
<TD>-2.781</TD>
<TD>0.153</TD>
<TD>-3.610</TD>
<TD>0.165</TD>
<TD>0.1448</TD>
<TD>0.1580</TD>
<TD>-0.3400</TD>
<TD>0.0468</TD>
<TD>0.0149</TD>
<TD>0.0299</TD>
<TD>-0.2083</TD>
<TD>-0.2178</TD>
<TD>0.0615</TD>
<TD>0.3266</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>1</TD>
<TD>0</TD>
<TD>0</TD>
<TD>5071.5</TD>
<TD>4.6975</TD>
<TD>-0.7109</TD>
<TD>2923.6248</TD>
<TD>0.0090</TD>
<TD></TD>
<TD>132513077885716709</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>URAT1-553569502</TD>
<TD></TD>
<TD></TD>
<TD>N2PD017266</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.78857456516</TD>
<TD>20.43013318223</TD>
</TR>
<TR>
<TD>307.7897816527446</TD>
<TD>+20.4315208963567</TD>
<TD>Gaia DR3 1816065554622254464</TD>
<TD>307.78977228741</TD>
<TD>+20.43150439876</TD>
<TD>1816065554622254464</TD>
<TD>0.3688</TD>
<TD>0.3801</TD>
<TD>-0.6243</TD>
<TD>0.5059</TD>
<TD>4.205</TD>
<TD>-1.975</TD>
<TD>0.444</TD>
<TD>-3.712</TD>
<TD>0.498</TD>
<TD>0.2553</TD>
<TD>0.0889</TD>
<TD>-0.3890</TD>
<TD>-0.0365</TD>
<TD>-0.0773</TD>
<TD>-0.0307</TD>
<TD>-0.2998</TD>
<TD>-0.2452</TD>
<TD>0.0025</TD>
<TD>0.3249</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD>0</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>132513077897708356</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>N2PD096440</TD>
<TD></TD>
<TD></TD>
<TD></TD>
<TD>307.78978165274</TD>
<TD>20.43152089636</TD>
</TR>
</TABLEDATA>
</DATA>
</TABLE>
<INFO name="matches" value="5">matching records</INFO>
<INFO name="Warning" value="No center provided++++" />
<INFO name="Warning" value="increase the precision for computed column 14" />
<INFO name="Warning" value="increase the precision for computed column 14" />
<INFO name="Warning" value="truncated result (maxtup=5)" />
<INFO name="QUERY_STATUS" value="OVERFLOW"> value="truncated result (maxtup=5)"</INFO>
</RESOURCE>
</VOTABLE>
<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE version="1.1" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<INFO name="QUERY_STATUS" value="ERROR">
<DESCRIPTION>PhotCalID not found: zaada</DESCRIPTION>
</INFO>
</VOTABLE>
<?xml version="1.0" encoding="UTF-8"?>
<INSTANCE dmrole="" dmtype="Phot:PhotCal">
<ATTRIBUTE dmrole="Phot:PhotCal.identifier" dmtype="ivoa:string" value="SLOAN/SDSS.g/AB"/>
<INSTANCE dmrole="Phot:PhotCal.zeroPoint" dmtype="Phot:AsinhZeroPoint">
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.type" dmtype="ivoa:integer" value="1"/>
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.referenceMagnitudeValue" dmtype="ivoa:real" value="0"/>
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.referenceMagnitudeError" dmtype="ivoa:real" value="0"/>
<ATTRIBUTE dmrole="Phot:AsinhZeroPoint.softeningParameter" dmtype="ivoa:real" value="0.00000000009"/>
<INSTANCE dmrole="Phot:AsinhZeroPoint.flux" dmtype="Phot:Flux">
<ATTRIBUTE dmrole="Phot:Flux.ucd" dmtype="Phot:UCD" value="phot.flux.density"/>
<ATTRIBUTE dmrole="Phot:Flux.unitexpression" dmtype="ivoa:Unit" value="Jy"/>
<ATTRIBUTE dmrole="Phot:Flux.value" dmtype="ivoa:real" value="3631"/>
<ATTRIBUTE dmrole="Phot:Flux.error" dmtype="ivoa:real" value="0"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.magnitudeSystem" dmtype="Phot:MagnitudeSystem">
<ATTRIBUTE dmrole="Phot:MagnitudeSystem.type" dmtype="Phot:TypeOfMagSystem" value="AB"/>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotCal.photometryFilter" dmtype="Phot:photometryFilter">
<ATTRIBUTE dmrole="Phot:PhotometryFilter.fpsIdentifier" dmtype="ivoa:string" value="ivo://svo/fps"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.identifier" dmtype="ivoa:string" value="SLOAN/SDSS.g"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.name" dmtype="ivoa:string" value="SDSS.g"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.description" dmtype="ivoa:string" value="SDSS g full transmission"/>
<ATTRIBUTE dmrole="Phot:PhotometryFilter.bandName" dmtype="ivoa:string" value="g"/>
<INSTANCE dmrole="Phot:PhotCal.photometryFilter.bandwidth" dmtype="Phot:Bandwidth">
<ATTRIBUTE dmrole="Phot:Bandwidth.ucd" dmtype="Phot:UCD" value="instr.bandwidth"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.unitexpression" dmtype="ivoa:Unit" value=""/>
<ATTRIBUTE dmrole="Phot:Bandwidth.extent" dmtype="ivoa:real" value="1064.6831251068"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.start" dmtype="ivoa:real" value="3797.6384743979"/>
<ATTRIBUTE dmrole="Phot:Bandwidth.stop" dmtype="ivoa:real" value="3797.6384743979"/>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.transmissionCurve" dmtype="Phot:TransmissionCurve">
<INSTANCE dmrole="Phot:TransmissionCurve.access" dmtype="Phot:Access">
<ATTRIBUTE dmrole="Phot:Access.reference" dmtype="ivoa:anyURI" value="http://svo2.cab.inta-csic.es/theory/fps/fps.php?ID=SLOAN/SDSS.g"/>
<ATTRIBUTE dmrole="Phot:Access.size" dmtype="ivoa:integer" value="2"/>
<ATTRIBUTE dmrole="Phot:Access.format" dmtype="ivoa:string" value="application/x-votable+xml"/>
</INSTANCE>
</INSTANCE>
<INSTANCE dmrole="Phot:PhotometryFilter.spectralLocation" dmtype="Phot:SpectralLocation">
<ATTRIBUTE dmrole="Phot:SpectralLocation.ucd" dmtype="Phot:UCD" value="em.wl;meta.main"/>
<ATTRIBUTE dmrole="Phot:SpectralLocation.unitexpression" dmtype="ivoa:Unit" value="Angstrom"/>
<ATTRIBUTE dmrole="Phot:SpectralLocation.value" dmtype="ivoa:real" value="4702.4953002767"/>
</INSTANCE>
</INSTANCE>
</INSTANCE>
"""
This module contains test cases for validating the generation of mapping dictionaries that allow
to extract Mivot instances from INFO located in the VOTable header
"""
import pytest
from astropy.io.votable import parse
from astropy.utils.data import get_pkg_data_filename
from pyvo.utils import activate_features
from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot.writer.header_mapper import HeaderMapper
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
data_origin_mapping = {
"service_protocol":
"ivo://ivoa.net/std/ConeSearch/v1.03",
"request_date":
"2025-04-07T12:06:32",
"request": ("https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch"
"/I/329/urat1?RA=52.26708&DEC=59.94027&SR=0.05"),
"contact":
"cds-question@unistra.fr",
"server_software":
"7.4.6",
"publisher":
"CDS",
"dataOrigin": [{
"ivoid": "ivo://cds.vizier/i/329",
"creators": ["Zacharias N."],
"cites": "bibcode:2015AJ....150..101Z",
"original_date": "2015",
"reference_url": "https://cdsarc.cds.unistra.fr/viz-bin/cat/I/329",
"rights_uri": "https://cds.unistra.fr/vizier-org/licences_vizier.html",
"articles": [{
"editor": "Astronomical Journal (AAS)"
}]
}]
}
coosys_mappings = [{"spaceRefFrame": "ICRS", "epoch": "2345"}]
timesys_mappings = [{"timescale": "TCB"}]
@pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+")
def test_all():
"""
checks that the mapping dictionaries extracte from the VOTable match the expected ones
"""
votable = parse(get_pkg_data_filename("data/test.header_extraction.xml"))
builder = HeaderMapper(votable)
assert builder.extract_origin_mapping() == data_origin_mapping
assert builder.extract_coosys_mapping() == coosys_mappings
assert builder.extract_timesys_mapping() == timesys_mappings
@pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+")
def test_field_extraction():
"""
checks that the epochPosition mapping dictionaries extracted from
the VOTable columns match the expected ones
"""
votable = parse(get_pkg_data_filename("data/test.header_extraction.1.xml"))
builder = HeaderMapper(votable)
mapping, error_mapping = builder.extract_epochposition_mapping()
assert mapping == {"longitude": "RA_ICRS", "latitude": "DE_ICRS", "parallax": "Plx",
"pmLongitude": "pmRA", "pmLatitude": "pmDE", "radialVelocity": "RV"}
assert error_mapping == {"position": {"class": "PErrorSym2D",
"sigma1": "e_RA_ICRS", "sigma2": "e_DE_ICRS"},
"parallax": {"class": "PErrorSym1D", "sigma": "e_Plx"},
"properMotion": {"class": "PErrorSym2D",
"sigma1": "e_pmRA", "sigma2": "e_pmDE"},
"radialVelocity": {"class": "PErrorSym1D", "sigma": "e_RV"}}
votable = parse(get_pkg_data_filename("data/test.header_extraction.2.xml"))
builder = HeaderMapper(votable)
mapping, error_mapping = builder.extract_epochposition_mapping()
assert mapping == {"obsDate": {"dateTime": "ObsDate",
"representation": "iso"},
"longitude": "RAB1950",
"latitude": "DEB1950"
}
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
This module contains test cases for validating the functionality of MivotInstance, MivotAnnotations,
and related components in the pyvo.mivot package. These tests ensure that the classes behave as
expected, including error handling and XML generation for data models.
"""
import os
import pytest
from unittest.mock import patch
from astropy.io.votable import parse
from astropy.utils.data import get_pkg_data_contents, get_pkg_data_filename
from pyvo.utils import activate_features
from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.writer.instances_from_models import InstancesFromModels
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
# File paths for test data
votable_path = os.path.realpath(
os.path.join(__file__, "..", "data", "test.mivot_viewer.no_mivot.xml")
)
data_path = os.path.realpath(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "data")
)
@pytest.fixture()
def mocked_fps_grvs(mocker):
def callback(request, context):
return bytes(get_pkg_data_contents('data/filter_gaia_grvs.xml'), "utf8")
with mocker.register_uri(
'GET', 'http://svo2.cab.inta-csic.es/svo/theory/fps/fpsmivot.php?PhotCalID=GAIA/GAIA3.Grvs/AB',
content=callback
) as matcher:
yield matcher
@pytest.fixture()
def mocked_fps_grp(mocker):
def callback(request, context):
return bytes(get_pkg_data_contents('data/filter_gaia_grp.xml'), "utf8")
with mocker.register_uri(
'GET', 'http://svo2.cab.inta-csic.es/svo/theory/fps/fpsmivot.php?PhotCalID=GAIA/GAIA3.Grp/AB',
content=callback
) as matcher:
yield matcher
@pytest.fixture
def mocked_fps():
with patch('requests.get') as mock_get:
yield mock_get
@pytest.mark.filterwarnings("ignore:root:::")
def add_color(builder):
filter_ids = {"high": "GAIA/GAIA3.Grp/AB", "low": "GAIA/GAIA3.Grvs/AB"}
mapping = {"value": 8.76, "definition": "ColorIndex",
"error": {"class": "PErrorAsym1D", "low": 1, "high": 3}
}
semantics = {"description": "very nice color", "uri": "vocabulary#term", "label": "term"}
builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics)
@pytest.mark.filterwarnings("ignore:root:::")
def add_photometry(builder):
photcal_id = "GAIA/GAIA3.Grvs/AB"
mapping = {"value": "GRVSmag",
"error": {"class": "PErrorAsym1D", "low": 1, "high": 3}
}
semantics = {"description": "Grvs magnitude",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude",
"label": "magnitude"}
builder.add_mango_brightness(photcal_id=photcal_id, mapping=mapping, semantics=semantics)
def add_epoch_positon(builder):
frames = {"spaceSys": {"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER', "equinox": None},
"timeSys": {"timescale": "TCB", "refPosition": 'BARYCENTER'}}
mapping = {"longitude": "_RAJ2000", "latitude": "_DEJ2000",
"pmLongitude": "pmRA", "pmLatitude": "pmDE",
"parallax": "Plx", "radialVelocity": "RV",
"obsDate": {"representation": "mjd", "dateTime": 579887.6},
"correlations": {"isCovariance": True,
"longitudeLatitude": "RADEcor",
"latitudePmLongitude": "DEpmRAcor", "latitudePmLatitude": "DEpmDEcor",
"longitudePmLongitude": "RApmRAcor", "longitudePmLatitude": "RApmDEcor",
"longitudeParallax": "RAPlxcor", "latitudeParallax": "DEPlxcor",
"pmLongitudeParallax": "PlxpmRAcor", "pmLatitudeParallax": "PlxpmDEcor",
},
"errors": {"position": {"class": "PErrorSym2D", "sigma1": "e_RA_ICRS", "sigma2": "e_DE_ICRS"},
"properMotion": {"class": "PErrorSym2D", "sigma1": "e_pmRA", "sigma2": "e_pmDE"},
"parallax": {"class": "PErrorSym1D", "sigma": "e_Plx"},
"radialVelocity": {"class": "PErrorSym1D", "sigma": "e_RV"}
}
}
semantics = {"description": "6 parameters position",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location",
"label": "Astronomical location"}
builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics)
@pytest.mark.usefixtures("mocked_fps_grvs", "mocked_fps_grp")
@pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+")
def test_all_properties():
votable_filename = get_pkg_data_filename("data/test.mango_annoter.xml")
votable = parse(votable_filename)
builder = InstancesFromModels(votable, dmid="DR3Name")
# pylint: disable=E501
builder.add_query_origin(
{"service_protocol": "ASU",
"request_date": "2024-03-21T15:16:08",
"request": ("https://vizier.cds.unistra.fr/viz-bin/votable?-oc.form=dec&amp;-out.max=5&amp;"
"-out.add=_r&amp;-out.add=_RAJ,_DEJ&amp;-sort=_r&amp;-c.eq=J2000&amp;-c.r= 2&amp;"
"-c.u=arcmin&amp;-c.geom=r&amp;-source=I/355/gaiadr3&amp;-order=I&amp;"
"-out.orig=standard&amp;-out=DR3Name&amp;-out=RA_ICRS&amp;-out=DE_ICRS&amp;"
"-out=Source&amp;-out=e_RA_ICRS&amp;-out=e_DE_ICRS&amp;-out=Plx&amp;"
"-out=e_Plx&amp;-out=PM&amp;-out=pmRA&amp;-out=e_pmRA&amp;-out=pmDE&amp;"
"-out=e_pmDE&amp;-out=RADEcor&amp;-out=RAPlxcor&amp;-out=RApmRAcor&amp;"
"-out=RApmDEcor&amp;-out=DEPlxcor&amp;-out=DEpmRAcor&amp;-out=DEpmDEcor&amp;"
"-out=PlxpmRAcor&amp;-out=PlxpmDEcor&amp;-out=pmRApmDEcor&amp;-out=RV&amp;"
"-out=e_RV&amp;-out=Vbroad&amp;-out=GRVSmag&amp;-out=QSO&amp;-out=Gal&amp;-out=NSS&amp;"
"-out=XPcont&amp;-out=XPsamp&amp;-out=RVS&amp;-out=EpochPh&amp;-out=EpochRV&amp;"
"-out=MCMCGSP&amp;-out=MCMCMSC&amp;-out=And&amp;-out=Teff&amp;-out=logg&amp;"
"-out=[Fe/H]&amp;-out=Dist&amp;-out=A0&amp;-out=HIP&amp;-out=PS1&amp;-out=SDSS13&amp;"
"-out=SKYM2&amp;-out=TYC2&amp;-out=URAT1&amp;-out=AllWISE&amp;-out=APASS9&amp;"
"-out=GSC23&amp;-out=RAVE5&amp;-out=2MASS&amp;-out=RAVE6&amp;-out=RAJ2000&amp;"
"-out=DEJ2000&amp;"),
"contact": "cds-question@unistra.fr",
"server_software": "7.33.3",
"publisher": "CDS",
"dataOrigin": [{"ivoid": "ivo://cds.vizier/i/355",
"creators": ["Gaia collaboration"],
"cites": "bibcode:2022yCat.1355....0G, ""doi:10.26093/cds/vizier.1355",
"original_date": "2022",
"reference_url": "https://cdsarc.cds.unistra.fr/viz-bin/cat/I/355",
"rights_uri": "https://cds.unistra.fr/vizier-org/licences_vizier.html",
"articles": [{"identifier": "doi:10.1051/0004-6361/202039657e]",
"editor": "A&A"}]
}]
})
add_color(builder)
add_photometry(builder)
add_epoch_positon(builder)
builder.pack_into_votable()
XmlUtils.pretty_print(builder._annotation.mivot_block)
assert XmlUtils.strip_xml(builder._annotation.mivot_block) == (
XmlUtils.strip_xml(get_pkg_data_contents("data/reference/mango_object.xml"))
)
@pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+")
def test_extraction_from_votable_header():
""" test that the automatic mapping extraction is well connected with the annoter
"""
votable_filename = get_pkg_data_filename("data/test.header_extraction.1.xml")
votable = parse(votable_filename)
builder = InstancesFromModels(votable, dmid="URAT1")
builder.extract_frames()
builder.extract_data_origin()
epoch_position_mapping = builder.extract_epoch_position_parameters()
builder.add_mango_epoch_position(**epoch_position_mapping)
builder.pack_into_votable()
assert XmlUtils.strip_xml(builder._annotation.mivot_block) == (
XmlUtils.strip_xml(get_pkg_data_contents("data/reference/test_header_extraction.xml"))
)
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
This module contains test cases for validating the functionality of MivotInstance, MivotAnnotations,
and related components in the pyvo.mivot package. These tests ensure that the classes behave as
expected, including error handling and XML generation for data models.
"""
import os
import pytest
from pyvo.utils import activate_features
from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot.utils.exceptions import MappingError
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.writer.annotations import MivotAnnotations
from pyvo.mivot.writer.instance import MivotInstance
# Enable MIVOT-specific features in the pyvo library
activate_features("MIVOT")
# File paths for test data
votable_path = os.path.realpath(
os.path.join(__file__, "..", "data", "test.mivot_viewer.no_mivot.xml")
)
data_path = os.path.realpath(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "data")
)
@pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+")
def test_MivotInstance():
"""
Test the MivotInstance class for various operations including attribute addition,
reference addition, and XML generation. Verifies that invalid operations raise
the expected MappingError.
"""
with pytest.raises(MappingError):
MivotInstance("")
instance1 = MivotInstance("model:type.inst", dmid="id1")
with pytest.raises(MappingError):
instance1.add_attribute(
dmrole="model:type.inst.role1", value="value1", unit="m/s"
)
with pytest.raises(MappingError):
instance1.add_attribute(
dmtype="model:type.att1", dmrole="model:type.inst.role1"
)
with pytest.raises(MappingError):
instance1.add_reference(dmref="dmreference")
with pytest.raises(MappingError):
instance1.add_reference(dmrole="model:type.inst.role2")
with pytest.raises(MappingError):
instance1.add_instance("azerty")
instance1.add_reference(dmrole="model:type.inst.role2", dmref="dmreference")
instance1.add_attribute(
dmtype="model:type.att1",
dmrole="model:type.inst.role1",
value="*value1",
unit="m/s",
)
assert XmlUtils.strip_xml(instance1.xml_string()) == (
"<INSTANCEdmtype=model:type.instdmid=id1>"
+ "<REFERENCEdmrole=model:type.inst.role2dmref=dmreference/>"
+ "<ATTRIBUTEdmtype=model:type.att1dmrole=model:type.inst.role1unit=m/svalue=value1/>"
+ "</INSTANCE>"
)
@pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+")
def test_MivotAnnotations():
"""
Test the MivotAnnotations class for template and global instance addition. Verifies
that invalid operations raise the expected MappingError.
"""
mb = MivotAnnotations()
with pytest.raises(MappingError):
mb.add_templates(12)
with pytest.raises(MappingError):
mb.add_globals(12)
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from .annotations import MivotAnnotations
from .instance import MivotInstance
from .instances_from_models import InstancesFromModels
from .header_mapper import HeaderMapper
from .mango_object import MangoObject, Property
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
MivotAnnotations: A utility module to build and manage MIVOT annotations.
"""
import os
import logging
try:
import xmlschema
except ImportError:
xmlschema = None
# Use defusedxml only if already present in order to avoid a new dependency.
try:
from defusedxml import ElementTree as etree
except ImportError:
from xml.etree import ElementTree as etree
from astropy.io.votable.tree import VOTableFile, Resource
try:
from astropy.io.votable.tree import MivotBlock
except ImportError:
pass
from astropy.io.votable import parse
from astropy import version
from pyvo.utils.prototype import prototype_feature
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.utils.exceptions import MappingError, AstropyVersionException
from pyvo.mivot.writer.instance import MivotInstance
from pyvo.mivot.version_checker import check_astropy_version
__all__ = ["MivotAnnotations"]
@prototype_feature("MIVOT")
class MivotAnnotations:
"""
This module provides a class to construct, validate, and insert MIVOT
blocks into VOTable files.
The MIVOT block, represented as an XML structure, is used for
data model annotations in the IVOA ecosystem.
The main features are:
- Construct the MIVOT block step-by-step with various components.
- Validate the MIVOT block against the MIVOT XML schema (if ``xmlschema`` is installed).
- Embed the MIVOT block into an existing VOTable file.
The MIVOT block is constructed as a string to maintain compatibility with the Astropy API.
Attributes
----------
suggested_space_frames: string array, class attribute
A warning is emitted if a frame not in this list is used to build a space frame.
This list matches https://www.ivoa.net/rdf/refframe/2022-02-22/refframe.html.
suggested_ref_positions: string array, class attribute
A warning is emitted if a reference position not in this list is
used to build a space or a time frame.
suggested_time_frames: string array, class attribute
A warning is emitted if a frame not in this list is used to build a space frame.
This list matches https://www.ivoa.net/rdf/timescale/2019-03-15/timescale.html.
"""
def __init__(self):
"""
"""
# Dictionary containing models with their names as keys and URLs as values
self._models = {}
# str: Indicates the success status of the annotation process.s
self._report_status = True
# str: message associated with the report, used in the REPORT block.
self._report_message = "Generated by pyvo.mivot.writer"
# list(str or MivotInstance): GLOBALS blocks to be included in the MIVOT block.
self._globals = []
# list(str or MivotInstance): TEMPLATES blocks to be included in the MIVOT block.
self._templates = []
# str: An optional ID for the TEMPLATES block.
self._templates_id = ""
# str: list of the dmid of the INSTANCE stored in the GLOBALS
self._dmids = []
# str: Complete MIVOT block as a string
self._mivot_block = ""
@property
def mivot_block(self):
"""
Getter for the whole MIVOT block.
Returns
-------
str
Complete MIVOT block as a string.
"""
return self._mivot_block
def _get_report(self):
"""
Generate the <REPORT> component of the MIVOT block.
Returns
-------
str
The <REPORT> block as a string, indicating the success or failure of the process.
"""
if self._report_status:
return f'<REPORT status="OK">{self._report_message}</REPORT>'
else:
return f'<REPORT status="FAILED">{self._report_message}</REPORT>'
def _get_models(self):
"""
Generate the <MODEL> components of the MIVOT block.
Returns
-------
str
The <MODEL> components as a formatted string.
"""
models_block = ""
for key, value in self._models.items():
if value:
models_block += f'<MODEL name="{key}" url="{value}" />\n'
else:
models_block += f'<MODEL name="{key}" />\n'
return models_block
def _get_globals(self):
"""
Generate the <GLOBALS> component of the MIVOT block.
Returns
-------
str
The <GLOBALS> block as a formatted string.
"""
globals_block = "<GLOBALS>\n"
for glob in self._globals:
globals_block += f"{glob}\n"
globals_block += "</GLOBALS>\n"
return globals_block
def _get_templates(self):
"""
Generate the <TEMPLATES> component of the MIVOT block.
Returns
-------
str
The <TEMPLATES> block as a formatted string, or an empty string if no templates are defined.
"""
if not self._templates:
return ""
if not self._templates_id:
templates_block = "<TEMPLATES>\n"
else:
templates_block = f'<TEMPLATES tableref="{self._templates_id}">\n'
for templates in self._templates:
templates_block += f"{templates}\n"
templates_block += "</TEMPLATES>\n"
return templates_block
def build_mivot_block(self, *, templates_id=None, schema_check=True):
"""
Build a complete MIVOT block from the declared components and validates it
against the MIVOT XML schema.
Parameters
----------
templates_id : str, optional
The ID to associate with the <TEMPLATES> block. Defaults to None.
schema_check : boolean, optional
Skip the XSD validation if False (use to make test working in local mode).
Raises
------
Any exceptions raised during XML validation are not caught and must
be handled by the caller.
"""
if templates_id:
self._templates_id = templates_id
self._mivot_block = '<VODML xmlns="http://www.ivoa.net/xml/mivot">\n'
self._mivot_block += self._get_report()
self._mivot_block += "\n"
self._mivot_block += self._get_models()
self._mivot_block += "\n"
self._mivot_block += self._get_globals()
self._mivot_block += "\n"
self._mivot_block += self._get_templates()
self._mivot_block += "\n"
self._mivot_block += "</VODML>\n"
self._mivot_block = self.mivot_block.replace("\n\n", "\n")
self._mivot_block = XmlUtils.pretty_string(self._mivot_block)
if schema_check:
self.check_xml()
def add_templates(self, templates_instance):
"""
Add an <INSTANCE> element to the <TEMPLATES> block.
Parameters
----------
templates_instance : str or MivotInstance
The <INSTANCE> element to be added.
Raises
------
MappingError
If ``templates_instance`` is neither a string nor an instance of `MivotInstance`.
"""
if isinstance(templates_instance, MivotInstance):
self._templates.append(templates_instance.xml_string())
if templates_instance.dmid is not None:
self._dmids.append(templates_instance.dmid)
elif isinstance(templates_instance, str):
self._templates.append(templates_instance)
else:
raise MappingError(
"Instance added to templates must be a string or MivotInstance."
)
def add_globals(self, globals_instance):
"""
Add an <INSTANCE> block to the <GLOBALS> block.
Parameters
----------
globals_instance : str or MivotInstance
The <INSTANCE> block to be added.
Raises
------
MappingError
If ``globals_instance`` is neither a string nor an instance of `MivotInstance`.
"""
if isinstance(globals_instance, MivotInstance):
self._globals.append(globals_instance.xml_string())
if globals_instance.dmid is not None:
self._dmids.append(globals_instance.dmid)
elif isinstance(globals_instance, str):
self._globals.append(globals_instance)
else:
raise MappingError(
"Instance added to globals must be a string or MivotInstance."
)
def add_model(self, model_name, *, vodml_url=None):
"""
Add a <MODEL> element to the MIVOT block.
Parameters
----------
model_name : str
The short name of the model.
vodml_url : str, optional
The URL of the VO-DML file associated with the model.
"""
self._models[model_name] = vodml_url
def set_report(self, status, message):
"""
Set the <REPORT> element of the MIVOT block.
Parameters
----------
status : bool
The status of the annotation process. True for success, False for failure.
message : str
The message associated with the REPORT.
Notes
-----
If ``status`` is False, all components of the MIVOT block except MODEL and REPORT
are cleared.
"""
self._report_status = status
self._report_message = message
if not status:
self._globals = []
self._templates = []
def check_xml(self):
"""
Validate the MIVOT block against the MIVOT XML schema v1.0.
Raises
------
MappingError
If the validation fails.
Notes
-----
The schema (mivot 1.0) is loaded from a local file to avoid dependency on a remote service.
"""
# put here just to improve the test coverage
root = etree.fromstring(self._mivot_block)
mivot_block = XmlUtils.pretty_string(root, clean_namespace=False)
if not xmlschema:
logging.warning(
"XML validation skipped: no XML schema validator found. "
+ "Please install it (e.g., pip install xmlschema)."
)
return
schema = xmlschema.XMLSchema11(os.path.dirname(__file__) + "/mivot-v1.xsd")
try:
schema.validate(mivot_block)
except Exception as excep:
raise MappingError(f"Validation failed: {excep}") from excep
def insert_into_votable(self, votable_file, override=False):
"""
Insert the MIVOT block into a VOTable.
Parameters
----------
votable_file : str or VOTableFile
The VOTable to be annotated, either as a file path or a ``VOTableFile`` instance.
override : bool
If True, overrides any existing annotations in the VOTable.
Raises
------
MappingError
If a mapping block already exists and ``override`` is False.
"""
if not check_astropy_version():
raise AstropyVersionException(f"Astropy version {version.version} "
"is below the required version 6.0 for the use of MIVOT.")
if isinstance(votable_file, str):
votable = parse(votable_file)
elif isinstance(votable_file, VOTableFile):
votable = votable_file
else:
raise MappingError(
"votable_file must be a file path string or a VOTableFile instance, "
"not a {type(votable_file)}."
)
for resource in votable.resources:
if resource.type == "results":
for subresource in resource.resources:
if subresource.type == "meta":
if not override:
raise MappingError(
"A type='meta' resource already exists in the first 'result' resource."
)
else:
logging.info("Overriding existing type='meta' resource.")
break
mivot_resource = Resource()
mivot_resource.type = "meta"
mivot_resource.mivot_block = MivotBlock(self._mivot_block)
resource.resources.append(mivot_resource)
"""
``HeaderMapper`` class source
"""
import logging
from pyvo.mivot.glossary import Roles, EpochPositionAutoMapping
from pyvo.mivot.utils.dict_utils import DictUtils
class HeaderMapper:
"""
This utility class generates dictionaries from header elements of a VOTable.
These dictionaries are used as input parameters by `pyvo.mivot.writer.InstancesFromModels`
to create MIVOT instances that are placed in the GLOBALS block or in the TEMPLATES.
In the current implementation, the following elements can be extracted:
- COOSYS -> coords:SpaceSys
- TIMESYS - coords:TimeSys
- INFO -> mango:QueryOrigin
- FIELD -> mango:EpochPosition
"""
def __init__(self, votable):
"""
Constructor parameters:
Parameters
----------
votable : astropy.io.votable.tree.VOTableFile
parsed votable from which INFO element are processed
"""
self._votable = votable
def _check_votable_head_element(self, parameter):
"""
Check that the parameter is a valid value.
.. note::
Vizier uses the ``UNKNOWN`` word to tag not set values
"""
return parameter is not None and parameter != "UNKNOWN"
def _extract_query_origin(self):
"""
Create a mapping dictionary from ``INFO`` elements found
in the VOTable header.
This dictionary is used to populate the ``mango:QueryOrigin`` attributes
Returns
-------
dict
Dictionary that is part of the input parameter for
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_query_origin`
"""
mapping = {}
for info in self._votable.infos:
if info.name in Roles.QueryOrigin:
mapping[info.name] = info.value
return mapping
def _extract_data_origin(self, resource):
"""
Create a mapping dictionary from ``INFO`` elements found
in the header of the first VOTable resource.
This dictionary is used to populate the ``mango:QueryOrigin`` part of
the ``mango:QueryOrigin`` instance.
Returns
-------
dict
Dictionary that is part of the input parameter for
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_query_origin`
"""
mapping = {}
article = None
for info in resource.infos:
if info.name in Roles.DataOrigin or info.name == "creator":
if DictUtils.add_array_element(mapping, "dataOrigin"):
article = {}
if info.name != "creator":
article[info.name] = info.value
else:
DictUtils.add_array_element(article, "creators")
article["creators"].append(info.value)
for info in resource.infos:
art_ref = {}
if info.name in Roles.Article:
DictUtils.add_array_element(article, "articles")
art_ref[info.name] = info.value
if art_ref:
article["articles"].append(art_ref)
mapping["dataOrigin"].append(article)
return mapping
def extract_origin_mapping(self):
"""
Create a mapping dictionary from all VOTable ``INFO`` elements.
This dictionary is used to build a ``mango:QueryOrigin`` INSTANCE
- INFO elements located in the VOTable header are used to build the ``mango:QueryOrigin``
part which scope is the whole VOtable by construction (one query -> one VOTable)
- INFO elements located in the resource header are used to build the ``mango:DataOrigin``
part which scope is the data located in this resource.
Returns
-------
dict
Dictionary that can be used as input parameter for
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_query_origin`
"""
mapping = self._extract_query_origin()
for resource in self._votable.resources:
mapping = {**mapping, **self._extract_data_origin(resource)}
return mapping
def extract_coosys_mapping(self):
"""
Create a mapping dictionary for each ``COOSYS`` element found
in the first VOTable resource.
Returns
-------
[dict]
Array of dictionaries which items can be used as input parameter for
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_simple_space_frame`
"""
mappings = []
for resource in self._votable.resources:
for coordinate_system in resource.coordinate_systems:
mapping = {}
if not self._check_votable_head_element(coordinate_system.system):
logging.warning(f"Not valid COOSYS found: ignored in MIVOT: {coordinate_system}")
continue
mapping["spaceRefFrame"] = coordinate_system.system
if self._check_votable_head_element(coordinate_system.equinox):
mapping["equinox"] = coordinate_system.equinox
if self._check_votable_head_element(coordinate_system.epoch):
mapping["epoch"] = coordinate_system.epoch
mappings.append(mapping)
return mappings
def extract_timesys_mapping(self):
"""
Create a mapping dictionary for each ``TIMESYS`` element found
in the first VOTable resource.
.. note::
the ``origin`` attribute is not supported yet
Returns
-------
[dict]
Array of dictionaries which items can be used as input parameter for
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_simple_time_frame`
"""
mappings = []
for resource in self._votable.resources:
for time_system in resource.time_systems:
mapping = {}
if not self._check_votable_head_element(time_system.timescale):
logging.warning(f"Not valid TIMESYS found: ignored in MIVOT: {time_system}")
continue
mapping["timescale"] = time_system.timescale
if self._check_votable_head_element(time_system.refposition):
mapping["refPosition"] = time_system.refposition
mappings.append(mapping)
return mappings
def extract_epochposition_mapping(self):
"""
Analyze the FIELD UCD-s to infer a data mapping to the EpochPosition class.
This mapping covers the 6 parameters with the Epoch and their errors.
The correlation part is not covered since there is no specific UCD for this.
The UCD-s accepted for each parameter are defined in `pyvo.mivot.glossary`.
The error classes are hard-coded as the most likely types.
- PErrorSym2D for 2D parameters
- PErrorSym1D for 1D parameters
Returns
-------
(dict, dict)
A mapping proposal for the EpochPosiion + errors that can be used as input parameter
by :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position`.
"""
def _check_ucd(mapping_entry, ucd, mapping):
"""
Inner function checking that mapping_entry matches with ucd according to
`pyvo.mivot.glossary`
"""
if mapping_entry in mapping:
return False
dict_entry = getattr(EpochPositionAutoMapping, mapping_entry)
if isinstance(dict_entry, list):
return ucd in dict_entry
else:
return ucd.startswith(dict_entry)
def _check_obs_date(field):
""" check if the field can be interpreted as a value date time
This algorithm is a bit specific for Vizier CS
"""
xtype = field.xtype
unit = field.unit
representation = None
if xtype == "timestamp" or unit == "'Y:M:D'" or unit == "'Y-M-D'":
representation = "iso"
# let's assume that dates expressed as days are MJD
elif xtype == "mjd" or unit == "d":
representation = "mjd"
if representation is None and unit == "year":
representation = "year"
if representation is not None:
field_ref = field.ID if field.ID is not None else field.name
return {"dateTime": field_ref, "representation": representation}
return None
table = self._votable.get_first_table()
fields = table.fields
mapping = {}
error_mapping = {}
for field in fields:
ucd = field.ucd
for mapping_entry in Roles.EpochPosition:
if _check_ucd(mapping_entry, ucd, mapping) is True:
if mapping_entry == "obsDate":
if (obs_date_mapping := _check_obs_date(field)) is not None:
mapping[mapping_entry] = obs_date_mapping
else:
mapping[mapping_entry] = field.ID if field.ID is not None else field.name
# Once we got a parameter mapping, we look for its associated error
# This nested loop makes sure we never have error without value
for err_field in fields:
err_ucd = err_field.ucd
# We assume the error UCDs are the same the these of the
# related quantities but prefixed with "stat.error;" and without "meta.main" qualifier
if err_ucd == ("stat.error;" + ucd.replace(";meta.main", "")):
param_mapping = err_field.ID if err_field.ID is not None else err_field.name
if mapping_entry == "parallax":
error_mapping[mapping_entry] = {"class": "PErrorSym1D",
"sigma": param_mapping}
elif mapping_entry == "radialVelocity":
error_mapping[mapping_entry] = {"class": "PErrorSym1D",
"sigma": param_mapping}
elif mapping_entry == "longitude":
if "position" in error_mapping:
error_mapping["position"]["sigma1"] = param_mapping
else:
error_mapping["position"] = {"class": "PErrorSym2D",
"sigma1": param_mapping}
elif mapping_entry == "latitude":
if "position" in error_mapping:
error_mapping["position"]["sigma2"] = param_mapping
else:
error_mapping["position"] = {"class": "PErrorSym2D",
"sigma2": param_mapping}
elif mapping_entry == "pmLongitude":
if "properMotion" in error_mapping:
error_mapping["properMotion"]["sigma1"] = param_mapping
else:
error_mapping["properMotion"] = {"class": "PErrorSym2D",
"sigma1": param_mapping}
elif mapping_entry == "pmLatitude":
if "properMotion" in error_mapping:
error_mapping["properMotion"]["sigma2"] = param_mapping
else:
error_mapping["properMotion"] = {"class": "PErrorSym2D",
"sigma2": param_mapping}
return mapping, error_mapping
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
MivotInstance is a simple API for building MIVOT instances step by step.
A MIVOT instance is a MIVOT serialisation of an object whose attributes are set
with column values or literals.
A MIVOT instance can contain ATTRIBUTEs elements, COLLECTIONs of elements, or other INSTANCEs.
The MIVOT INSTANCE structure is defined by the data model on which the data is mapped.
"""
from pyvo.utils.prototype import prototype_feature
from pyvo.mivot.utils.exceptions import MappingError
from pyvo.mivot.utils.mivot_utils import MivotUtils
__all__ = ["MivotInstance"]
@prototype_feature("MIVOT")
class MivotInstance:
"""
API for building <INSTANCE> elements of MIVOT annotation step by step.
This class provides methods for incremental construction of a MIVOT instance.
It builds <INSTANCE> elements that can contain <ATTRIBUTE>, <INSTANCE>, and <REFERENCE>.
Support for <COLLECTION> elements is not yet implemented.
The main features are:
- Model-agnostic: The implementation is independent of any specific data model.
- Syntax validation: Ensures basic MIVOT syntax rules are followed.
- Context-agnostic: Ignores context-dependent syntax rules.
attributes
----------
_dmtype : string
Instance type (class VO-DML ID)
_dmrole : string
Role played by the instance in the context where it is used
(given by the VO-DML serialization of the model)
_dmid : string
Free identifier of the instance
"""
def __init__(self, dmtype, *, dmrole=None, dmid=None):
"""
Parameters
----------
dmtype : str
dmtype of the INSTANCE (mandatory)
dmrole : str, optional
dmrole of the INSTANCE
dmid : str, optional
dmid of the INSTANCE
Raises
------
MappingError
If ``dmtype`` is not provided
"""
if not dmtype:
raise MappingError("Cannot build an instance without dmtype")
self._dmtype = dmtype
self._dmrole = dmrole
self._dmid = MivotUtils.format_dmid(dmid)
self._content = []
@property
def dmid(self):
return self._dmid
def add_attribute(self, dmtype=None, dmrole=None, *, value=None, unit=None):
"""
Add an <ATTRIBUTE> element to the instance.
Parameters
----------
dmtype : str
dmtype of the ATTRIBUTE (mandatory)
dmrole : str
dmrole of the ATTRIBUTE (mandatory)
value : str or numerical, optional
ID of the column to set the attribute value.
If ref is a string starting with a * or is numerical,
it is considered as a value (* stripped)
as a ref otherwise
unit : str, optional
Unit of the attribute
Raises
------
MappingError
If ``dmtype`` or ``dmrole`` is not provided, or if both ``ref`` and ``value`` are not defined
"""
if not dmtype:
raise MappingError("Cannot add an attribute without dmtype")
if not dmrole:
raise MappingError("Cannot add an attribute without dmrole")
ref, literal = MivotUtils.get_ref_or_literal(value)
if not ref and not value:
raise MappingError("Cannot add an attribute without ref or value")
xml_string = f'<ATTRIBUTE dmtype="{dmtype}" dmrole="{dmrole}" '
if unit and unit != "None":
xml_string += f'unit="{unit}" '
if literal:
xml_string += f'value="{literal}" '
else:
xml_string += f'ref="{ref}" '
xml_string += " />"
self._content.append(xml_string)
def add_reference(self, dmrole=None, dmref=None):
"""
Add a <REFERENCE> element to the instance.
Parameters
----------
dmrole : str
dmrole of the REFERENCE (mandatory)
dmref : str
dmref of the REFERENCE (mandatory)
Raises
------
MappingError
If ``dmrole`` or ``dmref`` is not provided
"""
if not dmref:
raise MappingError("Cannot add a reference without dmref")
if not dmrole:
raise MappingError("Cannot add a reference without dmrole")
xml_string = f'<REFERENCE dmrole="{dmrole}" dmref="{dmref}" />'
self._content.append(xml_string)
def add_instance(self, mivot_instance):
"""
Add a nested <INSTANCE> element to the instance.
Parameters
----------
mivot_instance : MivotInstance
INSTANCE to be added
Raises
------
MappingError
If ``mivot_instance`` is not of type ``MivotInstance``
"""
if not isinstance(mivot_instance, MivotInstance):
raise MappingError("Instance added must be of type MivotInstance")
self._content.append(mivot_instance.xml_string())
def add_collection(self, dmrole, mivot_instances):
"""
to be documented
"""
dm_att = ""
if dmrole:
dm_att = f"dmrole=\"{dmrole}\""
self._content.append(f'<COLLECTION {dm_att}>')
for mivot_instance in mivot_instances:
if isinstance(mivot_instance, MivotInstance):
self._content.append(mivot_instance.xml_string())
else:
self._content.append(mivot_instance)
self._content.append("\n")
self._content.append("</COLLECTION>")
def xml_string(self):
"""
Build and serialize the <INSTANCE> element as a string.
Returns
-------
str
The string representation of the <INSTANCE> element
"""
xml_string = f'<INSTANCE dmtype="{self._dmtype}" '
if self._dmrole:
xml_string += f'dmrole="{self._dmrole}" '
if self._dmid:
xml_string += f'dmid="{self._dmid}" '
xml_string += ">\n"
for element in self._content:
xml_string += " " + element + "\n"
xml_string += "</INSTANCE>\n"
return xml_string
'''
Created on 6 Feb 2025
@author: laurentmichel
'''
import logging
import re
import requests
# Use defusedxml only if already present in order to avoid a new dependency.
try:
from defusedxml import ElementTree as etree
# Element not implemented in recent versions of defusedxml
from xml.etree.ElementTree import Element
except ImportError:
from xml.etree import ElementTree as etree
from xml.etree.ElementTree import Element
from pyvo.mivot.utils.mivot_utils import MivotUtils
from pyvo.mivot.utils.xml_utils import XmlUtils
from pyvo.mivot.utils.xpath_utils import XPath
from pyvo.mivot.utils.vocabulary import Att, Ele
from pyvo.mivot.utils.exceptions import MappingError
from pyvo.mivot.writer.annotations import MivotAnnotations
from pyvo.mivot.writer.mango_object import MangoObject
from pyvo.mivot.writer.instance import MivotInstance
from pyvo.mivot.writer.header_mapper import HeaderMapper
from pyvo.mivot.glossary import (
VodmlUrl, IvoaType, ModelPrefix, Url, CoordSystems)
class InstancesFromModels(object):
"""
**Top-level API class** that allows to create VOTable annotations with objects issued
from the Mango model and its imported classes.
The annotation structure is handled by the API based on mapping
rules provided by the user.
The classes currently supported are:
- Photometric Calibrations (PhotDM)
- Spatial and temporal coordinate systems (Coordinates DM)
- MangoObject (QueryOrigin, EpochPosition, Brightness and Color)
The mapping built by this API relates to the first table.
"""
def __init__(self, votable, *, dmid=None):
"""
Constructor parameters:
Parameters
----------
votable : astropy.io.votable.tree.VOTableFile
parsed votable to be annotated
dmid : string, optional (default is None)
Column identifier or value (is starts with ``*``) to be used as identifier of the
MangoObject mapping each data row
"""
self._votable = votable
self._table = votable.get_first_table()
self._header_mapper = HeaderMapper(self._votable)
self._mango_instance = MangoObject(self._table, dmid=dmid)
self._annotation = MivotAnnotations()
self._annotation.add_model("ivoa",
vodml_url=VodmlUrl.ivoa)
@property
def mivot_block(self):
return self._annotation.mivot_block
def _check_value_consistency(self, word, suggested_words):
"""
Utility checking that the word belongs to the list of suggested words
"""
if word.replace("*", "") not in suggested_words:
logging.warning("Ref frame %s is not in %s, make sure there is no typo",
word, suggested_words)
def extract_frames(self):
"""
Build space and time frames (``coords:SpaceSys`` and ``coords:TimeSys``) from
the INFO elements located in the header of the first VOTable resource.
The instances are added to the GLOBALS Mivot block.
.. note::
The VOTable can host multiple instances of COOSYS or TIMESYS, but in
the current implementation, only the first one is taken
Returns
-------
{"spaceSys": [{"dmid"}], "timeSys": [{"dmid"}]}
Dictionary of all dmid-s of the frames actually added to the annotations.
These dmid-s must be used by instances ATTRIBUTEs to their related frames.
This dictionary can be used ``frames`` parameter of ``add_mango_epoch_position``
"""
ids = {"spaceSys": {}, "timeSys": {}}
# take the first coord system assuming it the most relevant
for coosys_mapping in self._header_mapper.extract_coosys_mapping():
dmid = self.add_simple_space_frame(**coosys_mapping)
if "dmid" not in ids["spaceSys"]:
ids["spaceSys"]["dmid"] = dmid
for timesys_mapping in self._header_mapper.extract_timesys_mapping():
dmid = self.add_simple_time_frame(**timesys_mapping)
if "dmid" not in ids["timeSys"]:
ids["timeSys"]["dmid"] = dmid
return ids
def extract_data_origin(self):
"""
Build a ``mango:QueryOrigin`` from
the INFO elements located in the VOTable header.
The instance is added to the GLOBALS Mivot block.
Returns
-------
`MivotInstance`
The ``mango:QueryOrigin`` Mivot instance
"""
mapping = self._header_mapper.extract_origin_mapping()
return self.add_query_origin(mapping)
def extract_epoch_position_parameters(self):
"""
Build a dictionary with the 3 parameters required by
:py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position`
(frames, mapping, semantic).
- frames: references to the frames created from COOSYS/TIMSYS
(see :py:meth:`pyvo.mivot.writer.InstancesFromModels.extract_frames`
- mapping: inferred from the FIELD ucd-s
- semantics: hard-coded
This method does not add the ``EpochPosition`` property because the
mapping parameters often need first to be completed (or fixed) by hand.
This especially true for the correlations that cannot be automatically
detected in a generic way (no specific UCD)
.. code-block::
epoch_position_mapping = builder.extract_epoch_position_parameters()
builder.add_mango_epoch_position(**epoch_position_mapping)
Returns
-------
{"frames", "mapping", "semantics"}
Dictionary of mapping elements that can unpacked to feed ``add_mango_epoch_position()``.
"""
mapping, error_mapping = self._header_mapper.extract_epochposition_mapping()
mapping["errors"] = error_mapping
mapping["correlations"] = {}
semantics = {"description": "6 parameters position",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location",
"label": "Astronomical location"}
frames = self.extract_frames()
return {"frames": frames, "mapping": mapping, "semantics": semantics}
def add_photcal(self, filter_name):
"""
Add to the GLOBALS the requested photometric calibration as defined in PhotDM1.1
The MIVOT serialization is provided by the SVO Filter Profile Service
(https://ui.adsabs.harvard.edu/abs/2020sea..confE.182R/abstract)
It is returned as one block containing the whole PhotCal instance.
The filter instance is extracted from the PhotCal object, where it is
replaced with a REFERENCE.
This makes the filter available as a GLOBALS to objects that need it.
- The dmid of the PhotCal is ``_photcal_DMID`` where DMID is the formatted
version of ``filter_name``
(see `pyvo.mivot.utils.MivotUtils.format_dmid`).
- The dmid of the PhotFilter is ``_photfilter_DMID`` where DMID is the formatted
version of ``filter_name``.
Parameters
----------
filter_name : str
FPS identifier (SVO Photcal ID) of the request filter
Returns
-------
(str, str)
dmids of photcal and filter instances
"""
response = requests.get(Url.FPS + filter_name)
if (http_code := response.status_code) != 200:
logging.error("FPS service error: %s", http_code)
return None, None
# get the MIVOT serialization of the requested Photcal (as a string)
# FPS returns bytes but that might change
fps_reponse = response.content
try:
fps_reponse = fps_reponse.decode('utf-8')
except (UnicodeDecodeError, AttributeError):
pass
# basic parsing of the response to check if an error has been returned
if "<INFO name=\"QUERY_STATUS\" value=\"ERROR\">" in fps_reponse:
message = re.search(r'<DESCRIPTION>(.*)</DESCRIPTION>', fps_reponse).group(1)
raise MappingError(f"FPS service error: {message}")
# set the identifiers that will be used for both PhotCal and PhotFilter
cal_id = MivotUtils.format_dmid(filter_name)
photcal_id = f"_photcal_{cal_id}"
filter_id = f"_photfilter_{cal_id}"
# skip if the same dmid is already recorded
if cal_id in self._annotation._dmids:
logging.warning("An instance with dmid=%s has already been stored in GLOBALS: skip", cal_id)
return photcal_id, filter_id
self._annotation._dmids.append(cal_id)
logging.info("%s PhotCal can be referred with dmref='%s'", cal_id, photcal_id)
# parse the PhotCal and extract the PhotFilter node
photcal_block = etree.fromstring(fps_reponse)
filter_block = XPath.x_path_contains(photcal_block,
".//" + Ele.INSTANCE,
Att.dmtype,
"Phot:photometryFilter")[0]
# Tune the Photcal to be placed as a GLOBALS child (no role but an id)
# and remove the PhotFilter node which will be shifted at the GLOBALS level
del photcal_block.attrib["dmrole"]
photcal_block.set("dmid", photcal_id)
photcal_block.remove(filter_block)
# Tune the PhotFilter to be placed as GLOBALS child (no role but an id)
filter_role = filter_block.get("dmrole")
del filter_block.attrib["dmrole"]
filter_block.set("dmid", filter_id)
# Append a REFERENCE on the PhotFilter node to the PhotCal block
reference = Element("REFERENCE")
reference.set("dmrole", filter_role)
reference.set("dmref", filter_id)
photcal_block.append(reference)
self._annotation.add_model("ivoa", vodml_url=VodmlUrl.ivoa)
self._annotation.add_model("Phot", vodml_url=VodmlUrl.Phot)
# fix some FPS tweaks
photcal_block_string = XmlUtils.pretty_string(photcal_block, lshift=" ")
photcal_block_string = photcal_block_string.replace(
"<ATTRIBUTE dmrole=\"Phot:ZeroPoint.softeningParameter\" dmtype=\"ivoa:real\" value=\"\"/>",
"<!-- ATTRIBUTE dmrole=\"Phot:ZeroPoint.softeningParameter\""
" dmtype=\"ivoa:real\" value=\"\"/ -->")
filter_block_string = XmlUtils.pretty_string(filter_block, lshift=" ")
filter_block_string = filter_block_string.replace("Phot:photometryFilter", "Phot:PhotometryFilter")
filter_block_string = filter_block_string.replace("Phot:PhotCal.photometryFilter.bandwidth",
"Phot:PhotometryFilter.bandwidth")
self._annotation.add_globals(photcal_block_string)
self._annotation.add_globals(filter_block_string)
return photcal_id, filter_id
def add_simple_space_frame(self, spaceRefFrame="ICRS", refPosition="BARYCENTER",
equinox=None, epoch=None):
"""
Adds a SpaceSys instance to the GLOBALS block as defined in the Coordinates
data model V1.0 (https://ivoa.net/documents/Coords/20221004/index.html).
Notes:
- This function implements only the most commonly used features. Custom reference positions
for TOPOCENTER frames are not supported. However, methods for implementing the missing
features can be derived from this code.
- A warning is emitted if either ``spaceRefFrame`` or ``refPosition``
have unexpected values.
- No error is raised if the parameter values are inconsistent.
- The ``dmid`` of the time frame is built from ``spaceRefFrame``,
``refrefPosition_position`` and ``equinox``.
Parameters
----------
spaceRefFrame : str, optional, default "ICRS"
The reference frame for the space frame.
refPosition : str, optional, default "BARYCENTER"
The reference position for the space frame.
equinox : str, optional, default None
The equinox for the reference frame, if applicable.
epoch : str, optional, default None
The epoch for the reference location, if applicable.
Returns
-------
str
The actual dmid of the time frame INSTANCE
"""
# build the dmid
dmid = f"_spaceframe_{spaceRefFrame.replace('*', '')}"
if equinox:
dmid += f"_{equinox.replace('*', '')}"
if refPosition:
dmid += f"_{refPosition.replace('*', '')}"
# skip if the same dmid is already recorded
if dmid in self._annotation._dmids:
logging.warning("A spaceSys instance with dmid=%s has already been stored in GLOBALS: skip",
dmid)
return dmid
logging.info("spaceSys %s can be referred with dmref='%s'", spaceRefFrame, dmid)
self._annotation._dmids.append(dmid)
# add (or overwrite) used models
self._annotation.add_model(ModelPrefix.ivoa, vodml_url=VodmlUrl.ivoa)
self._annotation.add_model(ModelPrefix.coords, vodml_url=VodmlUrl.coords)
# check whether ref_frame and ref_position are set with appropriate values
self._check_value_consistency(spaceRefFrame, CoordSystems.space_frames)
self._check_value_consistency(refPosition, CoordSystems.ref_positions)
# Build the SpaceSys instance component by component
space_system_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:SpaceSys",
dmid=dmid)
# let's start with the space frame
space_frame_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:SpaceFrame",
dmrole=f"{ModelPrefix.coords}:PhysicalCoordSys.frame")
space_frame_instance.add_attribute(dmtype=IvoaType.string,
dmrole=f"{ModelPrefix.coords}:SpaceFrame.spaceRefFrame",
value=MivotUtils.as_literal(spaceRefFrame))
if equinox is not None:
space_frame_instance.add_attribute(dmtype=f"{ModelPrefix.coords}:Epoch",
dmrole=f"{ModelPrefix.coords}:SpaceFrame.equinox",
value=MivotUtils.as_literal(equinox))
# then let's build the reference position.
# The RefLocation type depends on the presence of an epoch (see coords DM)
if epoch is not None:
ref_position_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:CustomRefLocation",
dmrole=f"{ModelPrefix.coords}:SpaceFrame.refPosition")
ref_position_instance.add_attribute(dmtype=IvoaType.string,
dmrole=f"{ModelPrefix.coords}:CustomRefLocation.position",
value=MivotUtils.as_literal(refPosition))
ref_position_instance.add_attribute(dmtype="coords:Epoch",
dmrole=f"{ModelPrefix.coords}:CustomRefLocation.epoch",
value=MivotUtils.as_literal(epoch))
else:
ref_position_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:StdRefLocation",
dmrole=f"{ModelPrefix.coords}:SpaceFrame.refPosition")
ref_position_instance.add_attribute(dmtype=IvoaType.string,
dmrole=f"{ModelPrefix.coords}:StdRefLocation.position",
value=MivotUtils.as_literal(refPosition))
# and pack everything
space_frame_instance.add_instance(ref_position_instance)
space_system_instance.add_instance(space_frame_instance)
# add the SpaceSys instance to the GLOBALS block
self._annotation.add_globals(space_system_instance)
return dmid
def add_simple_time_frame(self, timescale="TCB", *, refPosition="BARYCENTER"):
"""
Adds a TimeSys instance to the GLOBALS block as defined in the Coordinates
data model V1.0 (https://ivoa.net/documents/Coords/20221004/index.html).
Notes:
- This function implements only the most commonly used features. *Custom reference directions*
are not supported. However, methods for implementing missing features can be derived from
this code.
- A warning is emitted if either ``timescale`` or ``refPosition`` have unexpected values.
- No error is raised if the parameter values are inconsistent.
- The ``dmid`` of the time rame is built from ``timescale`` and ``refPosition``.
Parameters
----------
timescale : str, optional, default "TCB"
The reference frame for the time frame.
refPosition : str, optional, default "BARYCENTER"
The reference position for the time frame.
Returns
-------
str
The actual dmid of the time frame INSTANCE
"""
# buikd the dmid
dmid = f"_timeframe_{timescale.replace('*', '')}"
if refPosition:
dmid += f"_{refPosition.replace('*', '')}"
dmid = MivotUtils.format_dmid(dmid)
# skip if the same dmid is already recorded
if dmid in self._annotation._dmids:
logging.warning("An timeSys instance with dmid=%s has already been stored in GLOBALS: skip", dmid)
return dmid
logging.info("timeSys %s can be referred with dmref='%s'", timescale, dmid)
self._annotation._dmids.append(dmid)
# add (or overwrite) used models
self._annotation.add_model(ModelPrefix.ivoa, vodml_url=VodmlUrl.ivoa)
self._annotation.add_model(ModelPrefix.coords, vodml_url=VodmlUrl.coords)
# check whether ref_frame and ref_position are set with appropriate values
self._check_value_consistency(timescale, CoordSystems.time_frames)
self._check_value_consistency(refPosition, CoordSystems.ref_positions)
# Build the TimeSys instance component by component
time_sys_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:TimeSys",
dmid=dmid)
# Let's start with the time frame
time_frame_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:TimeFrame",
dmrole=f"{ModelPrefix.coords}:PhysicalCoordSys.frame")
time_frame_instance.add_attribute(dmtype=IvoaType.string,
dmrole=f"{ModelPrefix.coords}:TimeFrame.timescale",
value=MivotUtils.as_literal(timescale))
# Then let's build the reference position
ref_position_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:StdRefLocation",
dmrole=f"{ModelPrefix.coords}:TimeFrame.refPosition")
ref_position_instance.add_attribute(dmtype=IvoaType.string,
dmrole=f"{ModelPrefix.coords}:StdRefLocation.position",
value=MivotUtils.as_literal(refPosition))
# pack everything
time_frame_instance.add_instance(ref_position_instance)
time_sys_instance.add_instance(time_frame_instance)
# add the TimeSys instance to the GLOBALS block
self._annotation.add_globals(time_sys_instance)
return dmid
def add_mango_brightness(self, *, photcal_id=None, mapping={}, semantics={}):
"""
Add a Mango ``Brightness`` instance to the current `MangoObject` with the specified
photometric calibration, using the mapping parameter to associate VOtable data with it.
This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic.
Parameters
----------
photcal_id : string, optional (default is None)
Filter profile service (http://svo2.cab.inta-csic.es/theory/fps/} identifier
of the desired photometric calibration. It is made of the filter identifier
followed by the photometric system (e.g. GAIA/GAIA3.Grvs/AB)
mapping : dict, optional (default to an empty dictionary ({})
A dictionary defining the mapping of values. It includes:
- mapping of the brightness value
- one separate block for the error specification
semantics : dict, optional (default to an empty dictionary ({})
A dictionary specifying semantic details to be added to the Mango property.
Returns
-------
`Property`
The Mango property
Notes
-----
The mapping example below maps the data of the GaiaD3 table that can be found
in the test suite. Notice that the (fake) error bounds are given as literalS.
.. code-block:: python
photcal_id="GAIA/GAIA3.Grvs/AB"
mapping={"value": "GRVSmag",
"error": { "class": "PErrorAsym1D", "low": 1, "high": 3}
}
semantics={"description": "Grvs magnitude",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude",
"label": "magnitude"}
builder = InstancesFromModels(votable, dmid="DR3Name")
builder.add_mango_magnitude(photcal_id=photcal_id, mapping=mapping, semantics=semantics)
"""
# make sure MANGO is mentioned in <MODELS>
self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango)
# Add the photometric calibration instance in <GLOBALS>
photcal_id, _ = self.add_photcal(photcal_id)
# Add the brightness property to the MANGO instance
return self._mango_instance.add_brightness_property(photcal_id,
mapping,
semantics=semantics)
def add_mango_color(self, *, filter_ids={}, mapping={}, semantics={}):
"""
Add a Mango ``Color`` instance to the current `MangoObject` with the specified
low and high filters, using the mapping parameter to associate VOtable data with it.
This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic.
Parameters
----------
filter_ids : string, optional (default to an empty dictionary {})
Filter profile service (http://svo2.cab.inta-csic.es/theory/fps/} identifiers
of the high and low photometric calibrations that contain the desired filters.
Identifiers are made of the filter identifier
followed by the photometric system (e.g. GAIA/GAIA3.Grvs/AB).
mapping : dict, optional (default to an empty dictionary ({})
A dictionary defining the mapping of values. It includes:
- The mapping of the color value and the color definition (ColorIndex or
HardnessRatio).
- One separate block for the error specification.
semantics : dict, optional (default to an empty dictionary {})
A dictionary specifying semantic details to be added to the Mango property.
Returns
-------
`Property`
The Mango property
Notes
-----
The mapping example below maps the data of the GaiaD3 table that can be found
in the test suite.
The (fake) color value is given as a literal, it does not refer to any table column.
.. code-block:: python
filter_ids={"low": "GAIA/GAIA3.Grp/AB", "high": "GAIA/GAIA3.Grvs/AB"}
mapping={"value": 0.08, "definition": "ColorIndex",
"error": { "class": "PErrorAsym1D", "low": 0.01, "high": 0.02}
}
semantics={"description": "Fake color index",
"uri": "http://astrothesaurus.org/uat/1553",
"label": "Spectral index"}
builder = InstancesFromModels(votable, dmid="DR3Name")
builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics)
"""
self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango)
filter_low_name = filter_ids["low"]
filter_high_name = filter_ids["high"]
_, filter_low_id = self.add_photcal(filter_low_name)
_, filter_high_id = self.add_photcal(filter_high_name)
return self._mango_instance.add_color_instance(filter_low_id,
filter_high_id,
mapping,
semantics=semantics)
def add_mango_epoch_position(self, *, frames={}, mapping={}, semantics={}):
"""
Add a Mango ``EpochPosition`` instance to the current `MangoObject` with the specified
frames and semantics, using the mapping parameter to associate VOtable data with it.
This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic.
Parameters
----------
frames : dict, optional (default to an empty dictionary {})
A dictionary specifying the frames (space and time coordinate systems) to be used.
Frames parameters are global, they cannot refer to table columns.
If a frame description contains the "dmid" key, that value will be used as an identifier
an already installed frame (e.g. with ``extract_frames()``). Otherwise the content of
the frame description is meant to be used in input parameter
for ``add_simple_(space)time_frame()``
mapping : dict, optional (default to an empty dictionary {})
A dictionary defining the mapping of values. It includes:
- A flat list for position parameters.
- Two separate blocks for correlations and error specifications.
semantics : dict, optional (default to an empty dictionary {})
A dictionary specifying semantic details to be added to the Mango property.
Returns
-------
`Property`
The Mango property
Notes
-----
The mapping example below maps the data of the GaiaD3 table that can be found in the test suite.
.. code-block:: python
frames={"spaceSys": {"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER', "equinox": None},
"timeSys": {"timescale": "TCB", "refPosition": 'BARYCENTER'}}
mapping={"longitude": "_RAJ2000", "latitude": "_DEJ2000",
"pmLongitude": "pmRA", "pmLatitude": "pmDE",
"parallax": "Plx", "radialVelocity": "RV",
"correlations": {"isCovariance": True, "longitudeLatitude": "RADEcor",
"latitudePmLongitude": "DEpmRAcor", "latitudePmLatitude": "DEpmDEcor",
"longitudePmLongitude": "RApmRAcor", "longitudePmLatitude": "RApmDEcor",
"longitudeParallax": "RAPlxcor", "latitudeParallax": "DEPlxcor",
"pmLongitudeParallax": "PlxpmRAcor", "pmLatitudeParallax": "PlxpmDEcor",
},
"errors": { "position": { "class": "PErrorSym2D", "sigma1": "e_RA_ICRS",
"sigma2": "e_DE_ICRS"},
"properMotion": { "class": "PErrorSym2D", "sigma1": "e_pmRA",
"sigma2": "e_pmDE"},
"parallax": { "class": "PErrorSym1D", "sigma": "e_Plx"},
"radialVelocity": { "class": "PErrorSym1D", "sigma": "e_RV"}
}
}
semantics={"description": "6 parameters position",
"uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location",
"label": "Astronomical location"}
builder = InstancesFromModels(votable, dmid="DR3Name")
builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics)
"""
self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango)
space_frame_id = ""
if "spaceSys" in frames and frames["spaceSys"]:
if "dmid" in frames["spaceSys"]:
space_frame_id = frames["spaceSys"]["dmid"]
else:
space_frame_id = self.add_simple_space_frame(*frames["spaceSys"])
time_frame_id = ""
if "timeSys" in frames and frames["timeSys"]:
if "dmid" in frames["timeSys"]:
time_frame_id = frames["timeSys"]["dmid"]
else:
time_frame_id = self.add_simple_time_frame(**frames["timeSys"])
return self._mango_instance.add_epoch_position(space_frame_id, time_frame_id, mapping,
semantics)
def add_query_origin(self, mapping={}):
"""
Add the Mango ``QueryOrigin`` instance to the current `MangoObject`.
This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic.
Parameters
----------
mapping : dict, optional (default to an empty dictionary {})
A dictionary defining the QueryOrigin fields. Mapped fields are global,
they cannot refer to table columns
Returns
-------
`MivotInstance`
Notes
-----
The partial mapping example below maps a fake QueryOrigin. A complete (long) example based on GaiaD3
can be found in the test suite.
.. code-block:: python
builder = InstancesFromModels(votable, dmid="DR3Name")
builder.add_query_origin(
{
"service_protocol": "ivo://ivoa.net/std/ConeSearch/v1.03",
"request_date": "2025-04-07T12:06:32",
"request": (
"https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch"
"/I/329/urat1?RA=52.26708&DEC=59.94027&SR=0.05"
),
"contact": "cds-question@unistra.fr",
"server_software": "7.4.6",
"publisher": "CDS",
"dataOrigin": [
{
"ivoid": "ivo://cds.vizier/i/329",
"creators": ["Zacharias N."],
"cites": "bibcode:2015AJ....150..101Z",
"original_date": "2015",
"reference_url": "https://cdsarc.cds.unistra.fr/viz-bin/cat/I/329",
"rights_uri": "https://cds.unistra.fr/vizier-org/licences_vizier.html",
"articles": [{"editor": "Astronomical Journal (AAS)"}],
}
],
}
)
"""
self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango)
query_origin_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:origin.QueryOrigin",
dmid="_origin")
MivotUtils.populate_instance(query_origin_instance, "QueryOrigin", mapping, self._table,
IvoaType.string, as_literals=True, package="origin")
if "dataOrigin" in mapping:
origins = []
data_origin_mappings = mapping["dataOrigin"]
for data_origin_mapping in data_origin_mappings:
data_origin_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:origin.DataOrigin")
MivotUtils.populate_instance(data_origin_instance, "DataOrigin",
data_origin_mapping, self._table,
IvoaType.string, as_literals=True, package="origin")
if "articles" in data_origin_mapping:
articles = []
for art_mapping in data_origin_mapping["articles"]:
art_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:origin.Article")
MivotUtils.populate_instance(art_instance, "Article", art_mapping,
self._table, IvoaType.string,
as_literals=True, package="origin")
articles.append(art_instance)
data_origin_instance.add_collection(f"{ModelPrefix.mango}:origin.DataOrigin.articles",
articles)
if "creators" in data_origin_mapping:
creators = []
for art_mapping in data_origin_mapping["creators"]:
creators.append(f"<ATTRIBUTE dmtype=\"ivoa:string\" value=\"{art_mapping}\" />")
data_origin_instance.add_collection(f"{ModelPrefix.mango}:origin.DataOrigin.creators",
creators)
origins.append(data_origin_instance)
query_origin_instance.add_collection(f"{ModelPrefix.mango}:origin.QueryOrigin.dataOrigin",
origins)
self._annotation.add_globals(query_origin_instance)
self._annotation._dmids.append("_origin")
return query_origin_instance
def pack_into_votable(self, *, report_msg="", sparse=False):
"""
Pack all mapped objects in the annotation block and put it in the VOTable.
Parameters
----------
report_msg: string, optional (default to an empty string)
Content of the REPORT Mivot tag
sparse: boolean, optional (default to False)
If True, all properties are added in a independent way to the the TEMPLATES.
They are packed in a MangoObject otherwise.
"""
self._annotation.set_report(True, report_msg)
if sparse is True:
for prop in self._mango_instance.mango_properties:
# Add each individual property to the TEMPLATES block
self._annotation.add_templates(prop.xml_string())
else:
# Pack the MangoObject and put it in the TEMPLATES
self._annotation.add_templates(self._mango_instance.get_mango_object(
with_origin=("_origin" in self._annotation._dmids)))
self._annotation.build_mivot_block()
self._annotation.insert_into_votable(self._votable, override=True)
'''
Created on 22 Jan 2025
@author: laurentmichel
'''
from pyvo.mivot.utils.exceptions import MappingError
from pyvo.mivot.utils.mivot_utils import MivotUtils
from pyvo.mivot.writer.instance import MivotInstance
from pyvo.mivot.glossary import (
IvoaType, ModelPrefix, Roles, CoordSystems)
class Property(MivotInstance):
"""
Class representing one property of a MangoInstance. MangoInstance property
instances are `pyvo.mivot.writer.MivotInstance` augmented with a semantics block.
"""
def __init__(self, dmtype=None, *, dmrole=None, dmid=None, semantics={}):
"""
Parameters
----------
dmtype : str
dmtype of the INSTANCE (mandatory)
dmrole : str, optional (default as None)
dmrole of the INSTANCE
dmid : str, optional (default as None)
dmid of the INSTANCE
semantics : dict, optional (default as {})
Mapping of the semantic block (supported key: descripton, uri, label)
Raises
------
MappingError
If ``dmtype`` is not provided
"""
super().__init__(dmtype, dmrole=dmrole, dmid=dmid)
if "description" in semantics:
# we assume the description as always being a literal
self.add_attribute(dmtype="ivoa:string",
dmrole="mango:Property.description",
value=f"*{semantics['description']}")
if "uri" in semantics or "label" in semantics:
semantics_instance = MivotInstance(dmtype="mango:VocabularyTerm",
dmrole="mango:Property.semantics")
if "uri" in semantics:
semantics_instance.add_attribute(dmtype="ivoa:string",
dmrole="mango:VocabularyTerm.uri",
value=f"*{semantics['uri']}")
if "label" in semantics:
semantics_instance.add_attribute(dmtype="ivoa:string", dmrole="mango:VocabularyTerm.label",
value=f"*{semantics['label']}")
self.add_instance(semantics_instance)
class MangoObject(object):
"""
This class handles all the components of a MangoObject (properties, origin, instance identifier).
It is meant to be used by `pyvo.mivot.writer.InstancesFromModels` but not by end users.
- There is one specific method for each supported property (EpochPosition, photometry and QueryOrigin).
- The internal structure of the classes is hard-coded in the class logic.
"""
def __init__(self, table, *, dmid=None):
'''
Constructor parameters:
parameters
----------
table : astropy.io.votable.tree.TableElement
VOTable table which data is mapped on Mango
dmid: stringn optional (default as None)
Reference of the column to be used as a MangoObject identifier
'''
self._table = table
self._properties = []
self._dmid = dmid
def _get_error_instance(self, class_name, dmrole, mapping):
"""
Private method building and returning an Mango error instance
Returns: MivotInstance
"""
prop_err_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:error.{class_name}",
dmrole=dmrole)
MivotUtils.populate_instance(prop_err_instance, class_name, mapping,
self._table, IvoaType.RealQuantity, package="error")
return prop_err_instance
def _add_epoch_position_correlations(self, **correlations):
"""
Private method building and returning the correlation block of the EpocPosition object.
Returns
-------
`Property`
The EpochPosition correlations component
"""
epc_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:EpochPositionCorrelations",
dmrole=f"{ModelPrefix.mango}:EpochPosition.correlations")
MivotUtils.populate_instance(epc_instance, "EpochPositionCorrelations",
correlations, self._table, IvoaType.real)
return epc_instance
def _add_epoch_position_errors(self, **errors):
"""
Private method building and returning the error block of the EpocPosition object.
Returns
-------
`Property`
The EpochPosition error instance
"""
err_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:EpochPositionErrors",
dmrole=f"{ModelPrefix.mango}:EpochPosition.errors")
for role, mapping in errors.items():
error_class = mapping["class"]
if (role in Roles.EpochPositionErrors
and error_class in ["PErrorSym2D", "PErrorSym1D", "PErrorAsym1D"]):
err_instance.add_instance(
self._get_error_instance(error_class,
f"{ModelPrefix.mango}:EpochPositionErrors.{role}",
mapping))
return err_instance
def _add_epoch_position_epoch(self, **mapping):
"""
Private method building and returning the observation date (DateTime) of the EpohPosition.
Parameters
----------
mapping: dict(representation, datetime)
Mapping of the DateTime fields
Returns
-------
`Property`
The EpochPosition observation date instance
"""
datetime_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:DateTime",
dmrole=f"{ModelPrefix.mango}:EpochPosition.obsDate")
representation = mapping.get("representation")
value = mapping["dateTime"]
if representation not in CoordSystems.time_formats:
raise MappingError(f"epoch representation {representation} not supported. "
f"Take on of {CoordSystems.time_formats}")
datetime_instance.add_attribute(IvoaType.string,
f"{ModelPrefix.mango}:DateTime.representation",
value=MivotUtils.as_literal(representation))
datetime_instance.add_attribute(IvoaType.datetime,
f"{ModelPrefix.mango}:DateTime.dateTime",
value=value)
return datetime_instance
def add_epoch_position(self, space_frame_id, time_frame_id, mapping, semantics):
"""
Add an ``EpochPosition`` instance to the properties of the current ``MangoObject``.
Both mapping and semantics arguments inherit from
`pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position`.
Parameters
----------
space_frame_id : string
Identifier (dmid) of space system INSTANCE located in the GLOBALS
time_frame_id : string
Identifier (dmid) of time system INSTANCE located in the GLOBALS
mapping : dict
Mapping of the EpochPosition fields
semantics : dict
Mapping of the MangoObject property
Returns
-------
`Property`
The EpochPosition instance
"""
ep_instance = Property(dmtype=f"{ModelPrefix.mango}:EpochPosition",
semantics=semantics)
MivotUtils.populate_instance(ep_instance, "EpochPosition",
mapping, self._table, IvoaType.RealQuantity)
if "obsDate" in mapping:
ep_instance.add_instance(self._add_epoch_position_epoch(**mapping["obsDate"]))
if "correlations" in mapping:
ep_instance.add_instance(self._add_epoch_position_correlations(**mapping["correlations"]))
if "errors" in mapping:
ep_instance.add_instance(self._add_epoch_position_errors(**mapping["errors"]))
if space_frame_id:
ep_instance.add_reference(dmrole=f"{ModelPrefix.mango}:EpochPosition.spaceSys",
dmref=space_frame_id)
if time_frame_id:
ep_instance.add_reference(dmrole=f"{ModelPrefix.mango}:EpochPosition.timeSys",
dmref=time_frame_id)
self._properties.append(ep_instance)
return ep_instance
def add_brightness_property(self, filter_id, mapping, semantics={}):
"""
Add a ``Brightness`` instance to the properties of the current ``MangoObject``.
Both mapping and semantics arguments inherit from
`pyvo.mivot.writer.InstancesFromModels.add_mango_brightness`.
Parameters
----------
filter_id : string
Identifier (dmid) of the PhotCal INSTANCE located in the GLOBALS
mapping : dict
Mapping of the EpochPosition fields
semantics : dict
Mapping of the MangoObject property
Returns
-------
`Property`
The Brightness instance
"""
# create the MIVOT instance mapping the MANGO property
mag_instance = Property(dmtype=f"{ModelPrefix.mango}:Brightness",
semantics=semantics)
# set MANGO property attribute
MivotUtils.populate_instance(mag_instance, "PhotometricProperty",
mapping, self._table, IvoaType.RealQuantity)
# build the error instance if it is mapped
if "error" in mapping:
error_mapping = mapping["error"]
error_class = error_mapping["class"]
mag_instance.add_instance(
self._get_error_instance(error_class,
f"{ModelPrefix.mango}:PhotometricProperty.error",
error_mapping))
# add MIVOT reference to the photometric calibration instance
mag_instance.add_reference(dmrole=f"{ModelPrefix.mango}:Brightness.photCal", dmref=filter_id)
self._properties.append(mag_instance)
return mag_instance
def add_color_instance(self, filter_low_id, filter_high_id, mapping, semantics={}):
"""
Add an ``Color`` instance to the properties of the current ``MangoObject``.
Both mapping and semantics arguments inherit from
`pyvo.mivot.writer.InstancesFromModels.add_mango_color`.
Parameters
----------
filter_low_id : string
Identifier (dmid) of the low energy Photfilter INSTANCE located in the GLOBALS
filter_high_id : string
Identifier (dmid) of the high energy Photfilter INSTANCE located in the GLOBALS
mapping : dict
Mapping of the EpochPosition fields
semantics : dict
Mapping of the MangoObject property
Returns
-------
`Property`
The Color instance
"""
error_mapping = mapping["error"]
mag_instance = Property(dmtype=f"{ModelPrefix.mango}:Color",
semantics=semantics)
coldef_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:ColorDef",
dmrole=f"{ModelPrefix.mango}:Color.colorDef")
mapped_roles = MivotUtils._valid_mapped_dmroles(mapping.items(), "Color")
def_found = False
for dmrole, column in mapped_roles:
if dmrole.endswith("definition"):
def_found = True
coldef_instance.add_attribute(dmtype="mango:ColorDefinition",
dmrole="mango:ColorDef.definition",
value=f"*{column}")
if not def_found:
raise MappingError("Missing color definition")
mapping.pop("definition")
MivotUtils.populate_instance(mag_instance, "PhotometricProperty", mapping,
self._table, IvoaType.RealQuantity)
coldef_instance.add_reference(dmrole=f"{ModelPrefix.mango}:ColorDef.low",
dmref=filter_low_id)
coldef_instance.add_reference(dmrole=f"{ModelPrefix.mango}:ColorDef.high",
dmref=filter_high_id)
error_class = error_mapping["class"]
mag_instance.add_instance(self._get_error_instance(error_class,
f"{ModelPrefix.mango}:PhotometricProperty.error",
error_mapping))
mag_instance.add_instance(coldef_instance)
self._properties.append(mag_instance)
return mag_instance
def get_mango_object(self, with_origin=False):
"""
Make and return the XML serialization of the MangoObject.
Parameters
----------
with_origin : bool
Ask for adding a reference (_origin) to the query origin possibly located in the GLOBALS
Returns
-------
string
The XML serialization of the MangoObject
"""
mango_object = MivotInstance(dmtype="mango:MangoObject", dmid=self._dmid)
if self._dmid:
ref, value = MivotUtils.get_ref_or_literal(self._dmid)
att_value = ref if ref else value
mango_object.add_attribute(dmrole="mango:MangoObject.identifier",
dmtype=IvoaType.string,
value=att_value)
if with_origin:
mango_object.add_reference("mango:MangoObject.queryOrigin", "_origin")
m_properties = []
for prop in self._properties:
m_properties.append(prop.xml_string())
mango_object.add_collection("mango:MangoObject.propertyDock", m_properties)
return mango_object
<!-- XML Schema for the VODML lite mapping L. Michel 06/2020 -->
<!-- MIVOT schema for the record -->
<!-- GL 2021-07-23: Refactoring towards using type definitions rather than
global elements. Following VOTable v 1.11 refactoring from 23-May-2006 LM
2021-08-25: add VOtable import, prefix prefixes with dm-mapping -->
<!-- ======================= -->
<!-- XSD sample http://users.polytech.unice.fr/~pfz/LANGDOC/COURS/EXEMPLES/XSD_DXS/chapter14.html -->
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" version="1.1" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.ivoa.net/xml/mivot" xmlns:dm-mapping="http://www.ivoa.net/xml/mivot">
<!-- Required to validate mapping block within a VOTable (LM 08/2021) -->
<xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.3" schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3"/>
<xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.2" schemaLocation="http://www.ivoa.net/xml/VOTable/v1.2"/>
<xs:import namespace="http://www.ivoa.net/xml/VOTable/v1.1" schemaLocation="http://www.ivoa.net/xml/VOTable/v1.1"/>
<!-- Top level structure of the mapping block -->
<xs:element name="VODML">
<xs:complexType>
<xs:sequence>
<xs:element name="REPORT" type="dm-mapping:Report" minOccurs="0" maxOccurs="1"/>
<xs:element name="MODEL" type="dm-mapping:Model" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="GLOBALS" type="dm-mapping:Globals" minOccurs="0" maxOccurs="1"/>
<xs:element name="TEMPLATES" type="dm-mapping:Templates" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<!-- MD proposal
<xs:assert
test="if (dm-mapping:REPORT[@status] = 'OK') then (count(dm-mapping:MODEL) > 0) else true()" />
<xs:assert
test="if (dm-mapping:REPORT[@status] = 'FAILED') then (count(dm-mapping:MODEL) >= 0) else true()" />
<xs:assert
test="if (count(dm-mapping:MODEL) = 0) then (count(dm-mapping:REPORT) = 1) else true()" />
<xs:assert
test="if (count(dm-mapping:REPORT) = 0) then (count(dm-mapping:MODEL) > 0) else true()" />
-->
</xs:complexType>
<!-- Make sure dmid is unique within the mapping block -->
<xs:unique name="Uniquedmid">
<xs:selector xpath=".//*"/>
<xs:field xpath="@dmid"/>
</xs:unique>
</xs:element>
<!-- Annotation process report-->
<xs:complexType name="Report" mixed="true">
<xs:attribute name="status" type="xs:string" use="required"/>
<xs:assert test="@status = 'OK' or @status = 'FAILED'"/>
</xs:complexType>
<!-- Declaration of one used model -->
<xs:complexType name="Model">
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="url" type="xs:anyURI"/>
<xs:assert test="@name != ''"/>
<xs:assert test="if (@url) then (@url != '') else true() "/>
</xs:complexType>
<!-- Mapping of the data that have a global scope (e.g. frames) -->
<xs:complexType name="Globals">
<xs:all>
<xs:element name="INSTANCE" type="dm-mapping:Instance" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="COLLECTION" type="dm-mapping:Collection" minOccurs="0" maxOccurs="unbounded"/>
</xs:all>
<xs:assert test="count (dm-mapping:INSTANCE[@dmrole != '']) eq 0"/>
<xs:assert test="count (dm-mapping:COLLECTION[@dmrole != '']) eq 0"/>
<xs:assert test="every $child in ./dm-mapping:COLLECTION satisfies $child/@dmid != '' "/>
<xs:assert test="every $child in ./dm-mapping:COLLECTION satisfies ( every $grandchild in $child/dm-mapping:INSTANCE satisfies ( count($grandchild/dm-mapping:PRIMARY_KEY) gt 0 ) )"/>
<xs:assert test="every $child in ./dm-mapping:COLLECTION satisfies ( every $grandchild in $child/dm-mapping:JOIN satisfies (count($grandchild/@dmref) or count($grandchild/@sourceref)))"/>
<xs:assert test="every $child in ./dm-mapping:COLLECTION satisfies count($child/dm-mapping:ATTRIBUTE) = 0"/>
<!--
<xs:assert
test="every $child in .//dm-mapping:ATTRIBUTE satisfies (not($child/@ref) or $child/@ref = '')" />
-->
</xs:complexType>
<!-- Mapping of the data contained in a particular table -->
<xs:complexType name="Templates">
<xs:sequence>
<xs:element name="WHERE" type="dm-mapping:Where" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="INSTANCE" type="dm-mapping:Instance" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:string" name="tableref"/>
<xs:assert test="if (@tableref) then (@tableref != '') else true() "/>
<xs:assert test="every $child in ./dm-mapping:INSTANCE satisfies (not($child/@dmrole) or $child/@dmrole = '') "/>
</xs:complexType>
<!-- Mapping of either a Datatype or an Objecttype -->
<xs:complexType name="Instance">
<xs:sequence>
<xs:choice maxOccurs="unbounded">
<xs:element name="PRIMARY_KEY" type="dm-mapping:PrimaryKey" minOccurs="0"/>
</xs:choice> <!-- can this now be an xs:all -->
<xs:choice maxOccurs="unbounded">
<xs:element name="REFERENCE" type="dm-mapping:Reference" minOccurs="0"/>
<xs:element name="ATTRIBUTE" type="dm-mapping:Attribute" minOccurs="0"/>
<xs:element name="INSTANCE" type="dm-mapping:Instance" minOccurs="0"/>
<xs:element name="COLLECTION" type="dm-mapping:Collection" minOccurs="0"/>
</xs:choice>
</xs:sequence>
<xs:attribute type="xs:string" name="dmrole" use="optional"/>
<xs:attribute type="xs:string" name="dmtype" use="required"/>
<xs:attribute type="xs:string" name="dmid"/>
<!-- xs:assert test="(not(./@dmid) and @dmrole != '') or @dmid != '' "
/> -->
<xs:assert test=" @dmtype != '' "/>
<xs:assert test="if (@dmid) then ( @dmid != '') else true() "/>
<xs:assert test="every $child in ./dm-mapping:INSTANCE satisfies $child/@dmrole != '' "/>
<xs:assert test="every $child in ./dm-mapping:COLLECTION satisfies $child/@dmrole != '' "/>
<xs:assert test="every $child in ./dm-mapping:COLLECTION satisfies not($child/@dmid) or $child/@dmid = '' "/>
<xs:assert test="every $child in ./dm-mapping:ATTRIBUTE satisfies $child/@dmrole != '' "/>
<xs:assert test="every $child in ./dm-mapping:REFERENCE satisfies $child/@dmrole != '' "/>
</xs:complexType>
<!-- Atomic attribute -->
<xs:complexType name="Attribute">
<xs:attribute type="xs:string" name="dmrole" use="optional"/>
<xs:attribute type="xs:string" name="dmtype" use="required"/>
<xs:attribute type="xs:string" name="ref"/>
<xs:attribute type="xs:string" name="value"/>
<xs:attribute type="xs:string" name="unit"/>
<xs:attribute type="xs:string" name="arrayindex"/>
<xs:assert test="if (./@arrayindex) then (./@ref) else true()"/>
<xs:assert test="(not(./@ref) and ./@value) or (not(./@value) and ./@ref) or (./@value and ./@ref)"/>
<xs:assert test="if (@ref) then (@ref != '') else true() "/>
<xs:assert test="if (@arrayindex) then (@arrayindex &gt;= '0') else true() "/>
<xs:assert test="@dmtype != '' "/>
</xs:complexType>
<!-- Data list mapping block -->
<xs:complexType name="Collection">
<xs:choice maxOccurs="unbounded">
<xs:element name="REFERENCE" type="dm-mapping:Reference" minOccurs="0"/>
<xs:element name="INSTANCE" type="dm-mapping:Instance" minOccurs="0"/>
<xs:element name="ATTRIBUTE" type="dm-mapping:Attribute" minOccurs="0"/>
<xs:element name="COLLECTION" type="dm-mapping:Collection" minOccurs="0"/>
<xs:element name="JOIN" type="dm-mapping:Join" minOccurs="0" maxOccurs="1"/>
</xs:choice>
<xs:attribute type="xs:string" name="dmrole" use="optional"/>
<xs:attribute type="xs:string" name="size"/>
<xs:attribute type="xs:string" name="dmid"/>
<xs:assert test="if (@dmid) then ( @dmid != '') else true() "/>
<xs:assert test="if (count(dm-mapping:JOIN) &gt; 0 ) then (count(dm-mapping:REFERENCE) = 0) else true()"/>
<xs:assert test="if (count(dm-mapping:JOIN) &gt; 0 ) then (count(dm-mapping:INSTANCE) = 0) else true()"/>
<xs:assert test="if (count(dm-mapping:JOIN) &gt; 0 ) then (count(dm-mapping:ATTRIBUTE) = 0) else true()"/>
<xs:assert test="if (count(dm-mapping:JOIN) &gt; 0 ) then (count(dm-mapping:COLLECTION) = 0) else true()"/>
<xs:assert test="count(dm-mapping:ATTRIBUTE) eq 0 or (count(dm-mapping:ATTRIBUTE) gt 0 and count(dm-mapping:REFERENCE) eq 0 and count(dm-mapping:INSTANCE) eq 0 and count(dm-mapping:JOIN) eq 0 and count(dm-mapping:COLLECTION) eq 0)"/>
<xs:assert test="count(dm-mapping:REFERENCE) eq 0 or (count(dm-mapping:REFERENCE) gt 0 and count(dm-mapping:ATTRIBUTE) eq 0 and count(dm-mapping:INSTANCE) eq 0 and count(dm-mapping:JOIN) eq 0 and count(dm-mapping:COLLECTION) eq 0)"/>
<xs:assert test="count(dm-mapping:INSTANCE) eq 0 or (count(dm-mapping:INSTANCE) gt 0 and count(dm-mapping:ATTRIBUTE) eq 0 and count(dm-mapping:REFERENCE) eq 0 and count(dm-mapping:COLLECTION) eq 0)"/>
<xs:assert test="count(dm-mapping:JOIN) eq 0 or (count(dm-mapping:JOIN) gt 0 and count(dm-mapping:ATTRIBUTE) eq 0 and count(dm-mapping:REFERENCE) eq 0 and count(dm-mapping:COLLECTION) eq 0)"/>
<xs:assert test="count(dm-mapping:COLLECTION) eq 0 or (count(dm-mapping:COLLECTION) gt 0 and count(dm-mapping:ATTRIBUTE) eq 0 and count(dm-mapping:REFERENCE) eq 0 and count(dm-mapping:INSTANCE) eq 0 and count(dm-mapping:JOIN) eq 0)"/>
<xs:assert test="count (dm-mapping:INSTANCE[@dmrole != '']) eq 0"/>
<xs:assert test="count (dm-mapping:ATTRIBUTE[@dmrole != '']) eq 0"/>
<xs:assert test="count (dm-mapping:COLLECTION[@dmrole != '']) eq 0"/>
<xs:assert test="count (dm-mapping:REFERENCE[@dmrole != '']) eq 0"/>
</xs:complexType>
<xs:complexType name="Reference">
<xs:sequence>
<xs:element name="FOREIGN_KEY" type="dm-mapping:ForeignKey" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:string" name="dmrole" use="optional"/>
<xs:attribute type="xs:string" name="sourceref"/>
<xs:attribute type="xs:string" name="dmref"/>
<xs:assert test="if (./@sourceref) then @sourceref != '' else true()"/>
<xs:assert test="if (./@dmref) then @dmref != '' else true()"/>
<xs:assert test="(./@dmref and not(./@sourceref)) or (not(./@dmref) and ./@sourceref)"/>
<xs:assert test="((count(dm-mapping:FOREIGN_KEY) &gt; 0 and ./@sourceref and not(./@dmref)) or (count(./dm-mapping:FOREIGN_KEY) eq 0 and not(./@sourceref) and ./@dmref))"/>
</xs:complexType>
<!-- Join with another table. -->
<xs:complexType name="Join">
<xs:sequence>
<xs:element name="WHERE" type="dm-mapping:Where" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:string" name="sourceref"/>
<xs:attribute type="xs:string" name="dmref"/>
<xs:assert test="if (./@sourceref) then @sourceref != '' else true()"/>
<xs:assert test="if (./@dmref) then @dmref != '' else true()"/>
<xs:assert test="((./@sourceref) and (./@dmref)) or not(./@sourceref) or (count(dm-mapping:WHERE) &gt; 0 and (./@sourceref))"/>
</xs:complexType>
<!-- Select table rows with value of the column @ref = @value -->
<xs:complexType name="Where">
<xs:attribute type="xs:string" name="foreignkey"/>
<xs:attribute type="xs:string" name="primarykey"/>
<xs:attribute type="xs:string" name="value"/>
<xs:assert test="(./@foreignkey and ./@primarykey and not(@value)) or (./@foreignkey and not(./@primarykey) and @value) or (not(./@foreignkey) and ./@primarykey and @value)"/>
<xs:assert test="if (./@foreignkey) then @foreignkey != '' else true()"/>
<xs:assert test="if (./@primarykey) then @primarykey != '' else true()"/>
</xs:complexType>
<xs:complexType name="PrimaryKey">
<xs:attribute type="xs:string" name="ref"/>
<xs:attribute type="xs:string" name="dmtype"/>
<xs:attribute type="xs:string" name="value"/>
<xs:assert test="./@ref or ./@value "/>
<xs:assert test="@dmtype != '' "/>
<xs:assert test="if (./@ref) then @ref != '' else true()"/>
<xs:assert test="(./@value and not(./@ref)) or (not(./@value) and ./@ref)"/>
</xs:complexType>
<xs:complexType name="ForeignKey">
<xs:attribute type="xs:string" name="ref" use="required"/>
<xs:assert test="@ref != ''"/>
</xs:complexType>
</xs:schema>
+3
-3

@@ -26,3 +26,3 @@ # This test job is separated out into its own workflow to be able to trigger separately

- name: Set up Python 3.12
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:

@@ -36,3 +36,3 @@ python-version: "3.12"

- name: Upload coverage to codecov
uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
with:

@@ -47,3 +47,3 @@ file: ./coverage.xml

- name: Set up Python 3.13
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:

@@ -50,0 +50,0 @@ python-version: "3.13-dev"

@@ -27,8 +27,8 @@ # Developer version testing is in separate workflow

include:
- name: py38 oldest dependencies, Linux
python-version: '3.8'
tox_env: py38-test-oldestdeps-alldeps
- name: py39 mandatory dependencies only, Linux
- name: py39 oldest dependencies, Linux
python-version: '3.9'
tox_env: py39-test
tox_env: py39-test-oldestdeps-alldeps
- name: py310 mandatory dependencies only, Linux
python-version: '3.10'
tox_env: py310-test
- name: py311 with online tests, Linux

@@ -44,3 +44,3 @@ python-version: '3.11'

- name: Set up Python
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:

@@ -66,9 +66,9 @@ python-version: ${{ matrix.python-version }}

- name: Set up Python
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
python-version: '3.12'
- name: Install tox
run: python -m pip install --upgrade tox
- name: Python 3.10 with latest astropy
run: tox -e py310-test-alldeps
- name: Python 3.12 with latest astropy
run: tox -e py312-test-alldeps

@@ -82,6 +82,6 @@

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up Python 3.8
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
- name: Set up Python 3.12
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.8
python-version: '3.12'
- name: Install tox

@@ -100,3 +100,3 @@ run: python -m pip install --upgrade tox

- name: Set up Python 3.10
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:

@@ -103,0 +103,0 @@ python-version: '3.10'

@@ -20,2 +20,3 @@ # Compiled files

.hypothesis
*api_sample*

@@ -22,0 +23,0 @@ # Sphinx

@@ -11,3 +11,7 @@ Adam Ginsburg <keflavich@gmail.com>

Hugo van Kemenade <hugovk@users.noreply.github.com>
Hugo van Kemenade <hugovk@users.noreply.github.com> <1324225+hugovk@users.noreply.github.com>
Manon Marchand <manonmarchand22@gmail.com>
Markus Demleitner <m@tfiu.de>
Markus Demleitner <m@tfiu.de> <msdemlei@ari.uni-heidelberg.de>
Laurent Michel <laurent.michel@astro.unistra.fr>
Pey Lian Lim <2090236+pllim@users.noreply.github.com>

@@ -14,0 +18,0 @@ Ray Plante <rplante@ncsa.uiuc.edu> <rplante@ncsa.illinois.edu>

@@ -0,1 +1,33 @@

1.7 (2025-06-01)
================
Enhancements and Fixes
----------------------
- Extend the MIVOT module with the ability to build annotations component by
component and put them into a VOTable. [#627]
- Add an API helping to map VOtable data in the Mango data model by using
MIVOT [#664]
- Make deletion of TAP jobs optional via a new ``delete`` kwarg. [#640]
- Correctly delete jobs in ``TAPService.run_async`` even when the server
returns an error. [#667]
- Change AsyncTAPJob.result to return None if no result is found explicitly.
[#644]
- Add a UAT constraint to the registry interface for constraining
subjects [#649]
- Fixed AttributeError when a capability has None standardID in SIA2Service.
[#669]
Deprecations and Removals
-------------------------
- Versions of Python <3.9 are no longer supported. [#639]
1.6.2 (2025-04-07)

@@ -70,8 +102,2 @@ ==================

Bug Fixes
---------
- Fix propagating some previously swallowed exceptions. [#614]
Deprecations and Removals

@@ -78,0 +104,0 @@ -------------------------

@@ -18,3 +18,3 @@ .. _pyvo-data-access:

>>> import pyvo as vo
>>> service = vo.dal.SIAService("http://dc.zah.uni-heidelberg.de/lswscans/res/positions/siap/siap.xml")
>>> service = vo.dal.SIAService("http://dc.g-vo.org/lswscans/res/positions/siap/siap.xml")
>>> print(service.description)

@@ -477,3 +477,3 @@ Scans of plates kept at Landessternwarte Heidelberg-Königstuhl. They

>>> size = Quantity(0.5, unit="deg")
>>> sia_service = vo.dal.SIAService("http://dc.zah.uni-heidelberg.de/hppunion/q/im/siap.xml")
>>> sia_service = vo.dal.SIAService("http://dc.g-vo.org/hppunion/q/im/siap.xml")
>>> sia_results = sia_service.search(pos=pos, size=size)

@@ -570,3 +570,3 @@

>>> scs_srv = vo.dal.SCSService('http://dc.zah.uni-heidelberg.de/arihip/q/cone/scs.xml')
>>> scs_srv = vo.dal.SCSService('http://dc.g-vo.org/arihip/q/cone/scs.xml')
>>> scs_results = scs_srv.search(pos=pos, radius=size)

@@ -760,3 +760,3 @@

>>> row.getdataurl()
'http://dc.zah.uni-heidelberg.de/getproduct/califa/datadr3/V500/NGC0551.V500.rscube.fits'
'http://dc.g-vo.org/getproduct/califa/datadr3/V500/NGC0551.V500.rscube.fits'
>>> type(row.getdataset())

@@ -763,0 +763,0 @@ <class 'urllib3.response.HTTPResponse'>

@@ -171,5 +171,7 @@ .. _pyvo-discover:

discoverer.set_services(
registry.search(registry.Author("Hubble, %")))
registry.search(registry.UAT("galaxies", expand_down=3)))
to query services that give a particular author. More realistically,
to query services that claim to deal with galaxies or perhaps more
specific concepts (although this *will* pull a lot of extra services
that the discoverer will just discard). More realistically,

@@ -176,0 +178,0 @@ ::

@@ -1,6 +0,6 @@

********************
MIVOT (`pyvo.mivot`)
********************
**********************
MIVOT (``pyvo.mivot``)
**********************
This module contains the new feature of annotations in VOTable.
This module contains the new feature of handling model annotations in VOTable.
Astropy version >= 6.0 is required.

@@ -31,3 +31,3 @@

Implementation Scope
--------------------
====================
This implementation is totally model-agnostic.

@@ -43,7 +43,7 @@

Some of the examples have been provided by a special end-point of the Vizier cone-search service
(https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch) that maps query results to this model.
(https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/) that maps query results to this model.
.. image:: _images/mangoEpochPosition.png
:width: 500
:alt: EpochPropagation class used to validate this api.
:width: 500
:alt: EpochPropagation class used to validate this api.

@@ -61,189 +61,18 @@ It is to be noted that the Vizier service does not annotate errors at the time of writing (Q1 2024)

- ``JOIN`` features are not supported.
- ``TEMPLATES`` with more than one ``INSANCE`` not supported.
- ``TEMPLATES`` with more than one ``INSTANCE`` not supported.
Integrated Readout
------------------
The ``ModelViewer`` module manages access to data mapped to a model through dynamically
generated objects (``MivotInstance``class).
The example below shows how a VOTable, resulting from a cone-search query which data are mapped
to the ``EpochPosition`` class, can be consumed.
.. doctest-remote-data::
>>> import astropy.units as u
>>> from astropy.coordinates import SkyCoord
>>> from pyvo.dal.scs import SCSService
>>> from pyvo.utils.prototype import activate_features
>>> from pyvo.mivot.version_checker import check_astropy_version
>>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer
>>> activate_features("MIVOT")
>>> if check_astropy_version() is False:
... pytest.skip("MIVOT test skipped because of the astropy version.")
>>> scs_srv = SCSService("https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch/I/239/hip_main")
>>> m_viewer = MivotViewer(
... scs_srv.search(
... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'),
... radius=0.05
... )
... )
>>> mivot_instance = m_viewer.dm_instance
>>> print(mivot_instance.dmtype)
mango:EpochPosition
>>> print(mivot_instance.coordSys.spaceRefFrame.value)
ICRS
>>> while m_viewer.next():
... print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}")
position: 59.94033461 52.26722684
Using the MIVOT package
=======================
The ``pyvo.mivot`` module can be used to either read or build annotations.
In this example, the data readout is totally managed by the ``MivotViewer`` instance.
The ``astropy.io.votable`` API is encapsulated in this module.
Model leaves (class attributes) are complex types that provide additional information:
- ``value``: attribute value
- ``dmtype``: attribute type such as defined in the Mivot annotations
- ``unit``: attribute unit such as defined in the Mivot annotations
- ``ref``: identifier of the table column mapped on the attribute
The model view on a data row can also be passed as a Python dictionary
using the ``to_dict()`` method of ``MivotInstance``.
.. code-block:: python
:caption: Working with a model view as a dictionary
(the JSON layout has been squashed for display purpose)
from pyvo.mivot import MivotViewer
from pyvo.mivot.utils.dict_utils import DictUtils
m_viewer = MivotViewer(path_to_votable)
mivot_instance = m_viewer.dm_instance
mivot_object_dict = mivot_object.to_dict()
DictUtils.print_pretty_json(mivot_object_dict)
{
"dmtype": "mango:EpochPosition",
"longitude": {"value": 359.94372764, "unit": "deg"},
"latitude": {"value": -0.28005255, "unit": "deg"},
"pmLongitude": {"value": -5.14, "unit": "mas/yr"},
"pmLatitude": {"value": -25.43, "unit": "mas/yr"},
"epoch": {"value": 1991.25, "unit": "year"},
"coordSys": {
"dmtype": "coords:SpaceSys",
"dmid": "ICRS",
"dmrole": "coords:Coordinate.coordSys",
"spaceRefFrame": {"value": "ICRS"},
},
}
- It is recommended to use a copy of the
dictionary as it will be rebuilt each time the ``to_dict()`` method is invoked.
- The default representation of ``MivotInstance`` instances is made with a pretty
string serialization of this dictionary (method ``__repr__()``).
- An extended version of the object dictionary e.g. with information about where
the values were picked from from, is available using the method ``to_hk_dict()``.
Per-Row Readout
---------------
The annotation schema can also be applied to table rows read outside of the ``MivotViewer``
with the `astropy.io.votable` API:
.. code-block:: python
:caption: Accessing the model view of Astropy table rows
votable = parse(path_to_votable)
table = votable.resources[0].tables[0]
# init the viewer
mivot_viewer = MivotViewer(votable, resource_number=0)
mivot_object = mivot_viewer.dm_instance
# and feed it with the table row
read = []
for rec in table.array:
mivot_object.update(rec)
read.append(mivot_object.longitude.value)
# show that the model retrieve the correct data values
assert rec["RAICRS"] == mivot_object.longitude.value
assert rec["DEICRS"] == mivot_object.latitude.value
In this case, it is up to the user to ensure that the read data rows are those mapped by the Mivot annotations.
Get a SkyCoord Instance Directly From the Annotations
-----------------------------------------------------
Once you get a ``MivotInstance`` representing the last row read, you can use it to create an ``astropy.SkyCoord`` object.
.. code-block:: python
:caption: Accessing the model view of Astropy table rows
from pyvo.mivot import MivotViewer
m_viewer = MivotViewer(path_to_votable)
mivot_instance = m_viewer.dm_instance
print(mivot_instance.get_SkyCoord())
<SkyCoord (ICRS): (ra, dec) in deg(52.26722684, 59.94033461)
(pm_ra_cosdec, pm_dec) in mas / yr(-0.82, -1.85)>
This feature works under the condition that the annotations contain a valid instance of ``mango:EPochPosition``, otherwise
a ``NoMatchingDMTypeError`` is thrown.
Although not a standard at the time of writing, the class structure supported by this implementation must match the figure above.
For XML Hackers
---------------
The model instances can also be serialized as XML elements that can be parsed with XPath queries.
.. code-block:: python
:caption: Accessing the XML view of the mapped model instances
with MivotViewer(path_to_votable) as mivot_viewer:
while mivot_viewer.next():
xml_view = mivot_viewer.xml_view
# do whatever you want with this XML element
It to be noted that ``mivot_viewer.xml_view`` is a shortcut
for ``mivot_viewer.xml_view.view`` where ``mivot_viewer.xml_view``
is is an instance of ``pyvo.mivot.viewer.XmlViewer``.
This object provides many functions facilitating the XML parsing.
Class Generation in a Nutshell
------------------------------
MIVOT reconstructs model structures with 3 elements:
- ``INSTANCE`` for the objects
- ``ATTRIBUTE`` for the attributes
- ``COLLECTION`` for the elements with a cardinality greater than 1
The role played by each of these elements in the model hierarchy is defined
by its ``@dmrole`` XML attribute. Types of both ``INSTANCE`` and ``ATTRIBUTE`` are defined by
their ``@dmtype`` XML attributes.
``MivotInstance`` classes are built by following MIVOT annotation structure:
- ``INSTANCE`` are represented by Python classes
- ``ATTRIBUTE`` are represented by Python class fields
- ``COLLECTION`` are represented by Python lists ([])
``@dmrole`` and ``@dmtype`` cannot be used as Python keywords as such, because they are built from VO-DML
identifiers, which have the following structure: ``model:a.b``.
- Only the last part of the path is kept for attribute names.
- For class names, forbidden characters (``:`` or ``.``) are replaced with ``_``.
- Original ``@dmtype`` are kept as attributes of generated Python objects.
- The structure of the ``MivotInstance`` objects can be inferred from the mapped model in 2 different ways:
- 1. From the MIVOT instance property ``MivotInstance.to_dict()`` a shown above.
This is a pure Python dictionary but its access can be slow because it is generated
on the fly each time the property is invoked.
- 2. From the internal class dictionary ``MivotInstance.__dict__``
(see the Python `data model <https://docs.python.org/3/reference/datamodel.html>`_).
Reference/API
=============
.. automodapi:: pyvo.mivot
.. automodapi:: pyvo.mivot.viewer
.. automodapi:: pyvo.mivot.seekers
.. automodapi:: pyvo.mivot.features
.. automodapi:: pyvo.mivot.utils
.. toctree::
:maxdepth: 2
viewer
annoter
writer
example
annoter_tips

@@ -57,2 +57,4 @@ .. _pyvo-registry:

UCD (e.g., ``phot.mag;em.ir.%`` for “any infrared magnitude”).
* :py:class:`~pyvo.registry.UAT` (``uat``): constrain by concepts
from the IVOA Unified Astronomy Thesaurus http://www.ivoa.net/rdf/uat.
* :py:class:`~pyvo.registry.Waveband` (``waveband``): one or more terms

@@ -101,8 +103,21 @@ from the vocabulary at http://www.ivoa.net/rdf/messenger giving the rough

or a mixture between the two. Constructing using explicit
constraints is generally preferable with more complex queries. Where
the constraints accept multiple arguments, you can pass in sequences to
the keyword arguments; for instance:
constraints is generally preferable with more complex queries.
An advantage of using explicit constraints is that you can pass
additional parameters to the constraints. For instance, the UAT
constraint can optionally expand your keyword to narrower or wider
concepts. When looking for resources talking about Cepheids of all
kinds, you can thus say:
.. doctest-remote-data::
>>> resources = registry.search(
... registry.UAT("cepheid-variable-stars", expand_down=3))
There is no way to express this using keyword arguments.
However, where the constraints accept multiple equivalent arguments, you
can pass in sequences to the keyword arguments; for instance:
.. doctest-remote-data::
>>> resources = registry.search(registry.Waveband("Radio", "Millimeter"),

@@ -118,2 +133,3 @@ ... registry.Author("%Miller%"))

There is also :py:meth:`~pyvo.registry.get_RegTAP_query`, accepting the

@@ -244,4 +260,4 @@ same arguments as :py:meth:`pyvo.registry.search`. This function simply

... print(interface)
Interface(type='tap#aux', description='', url='http://tapvizier.cds.unistra.fr/TAPVizieR/tap')
Interface(type='vr:webbrowser', description='', url='http://vizier.cds.unistra.fr/viz-bin/VizieR-2?-source=J/ApJ/727/14')
Interface(type='tap#aux', description='', url='https://tapvizier.cds.unistra.fr/TAPVizieR/tap')
Interface(type='vr:webbrowser', description='', url='https://vizier.cds.unistra.fr/viz-bin/VizieR-2?-source=J/ApJ/727/14')
Interface(type='conesearch', description='Cone search capability for table J/ApJ/727/14/table2 (AKARI IRC 3-24{mu}m, and Spitzer MIPS 24/70{mu}m photometry of Abell 2255 member galaxies)', url='https://vizier.cds.unistra.fr/viz-bin/conesearch/J/ApJ/727/14/table2?')

@@ -254,3 +270,3 @@

>>> voresource.list_interfaces()[0].to_service()
TAPService(baseurl : 'http://tapvizier.cds.unistra.fr/TAPVizieR/tap', description : '')
TAPService(baseurl : 'https://tapvizier.cds.unistra.fr/TAPVizieR/tap', description : '')

@@ -263,3 +279,3 @@ The list of interfaces can also be filtered to interfaces corresponding to services of a

>>> voresource.list_interfaces("tap")
[Interface(type='tap#aux', description='', url='http://tapvizier.cds.unistra.fr/TAPVizieR/tap')]
[Interface(type='tap#aux', description='', url='https://tapvizier.cds.unistra.fr/TAPVizieR/tap')]

@@ -305,5 +321,4 @@ To operate TAP services, you need to know what tables make up a

also want to have a look at Aladin's discovery tree, TOPCAT's VO menu,
or at services like DataScope_ or WIRR_ in your web browser.
or at services like WIRR_ in your web browser.
.. _DataScope: https://heasarc.gsfc.nasa.gov/cgi-bin/vo/datascope/init.pl
.. _WIRR: https://dc.g-vo.org/WIRR

@@ -310,0 +325,0 @@

Metadata-Version: 2.4
Name: pyvo
Version: 1.6.2
Version: 1.7
Summary: Astropy affiliated package for accessing Virtual Observatory data and services

@@ -16,5 +16,5 @@ Author: the PyVO Developers

Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.8
Requires-Python: >=3.9
License-File: LICENSE.rst
Requires-Dist: astropy>=4.1
Requires-Dist: astropy>=4.2
Requires-Dist: requests

@@ -65,3 +65,3 @@ Provides-Extra: all

Source code can be found `on GitHub <http://github.com/astropy/pyvo>`_
Source code can be found `on GitHub <https://github.com/astropy/pyvo>`_

@@ -71,3 +71,3 @@ Installation and Requirements

Releases of PyVO are available from `PyPI <https://pypi.python.org/pypi/pyvo>`_
Releases of PyVO are available from `PyPI <https://pypi.org/project/pyvo/>`_
thus, it and its prerequisites can be most easily installed using ``pip``:

@@ -87,3 +87,3 @@

* `astropy <https://astropy.org>`__ (>=4.1)
* `requests <http://docs.python-requests.org/en/latest/>`_
* `requests <https://docs.python-requests.org/en/latest/>`_

@@ -124,3 +124,3 @@ The following packages are optional dependencies and are required for the

Many instructive examples can be found in the `PyVO Documentation <http://pyvo.readthedocs.org>`_.
Many instructive examples can be found in the `PyVO Documentation <https://pyvo.readthedocs.io/en/latest/>`_.
Additional examples can be found in the examples directory.

@@ -127,0 +127,0 @@

Metadata-Version: 2.4
Name: pyvo
Version: 1.6.2
Version: 1.7
Summary: Astropy affiliated package for accessing Virtual Observatory data and services

@@ -16,5 +16,5 @@ Author: the PyVO Developers

Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.8
Requires-Python: >=3.9
License-File: LICENSE.rst
Requires-Dist: astropy>=4.1
Requires-Dist: astropy>=4.2
Requires-Dist: requests

@@ -65,3 +65,3 @@ Provides-Extra: all

Source code can be found `on GitHub <http://github.com/astropy/pyvo>`_
Source code can be found `on GitHub <https://github.com/astropy/pyvo>`_

@@ -71,3 +71,3 @@ Installation and Requirements

Releases of PyVO are available from `PyPI <https://pypi.python.org/pypi/pyvo>`_
Releases of PyVO are available from `PyPI <https://pypi.org/project/pyvo/>`_
thus, it and its prerequisites can be most easily installed using ``pip``:

@@ -87,3 +87,3 @@

* `astropy <https://astropy.org>`__ (>=4.1)
* `requests <http://docs.python-requests.org/en/latest/>`_
* `requests <https://docs.python-requests.org/en/latest/>`_

@@ -124,3 +124,3 @@ The following packages are optional dependencies and are required for the

Many instructive examples can be found in the `PyVO Documentation <http://pyvo.readthedocs.org>`_.
Many instructive examples can be found in the `PyVO Documentation <https://pyvo.readthedocs.io/en/latest/>`_.
Additional examples can be found in the examples directory.

@@ -127,0 +127,0 @@

@@ -1,2 +0,2 @@

astropy>=4.1
astropy>=4.2
requests

@@ -3,0 +3,0 @@

@@ -16,2 +16,3 @@ .gitignore

.github/dependabot.yml
.github/release.yml
.github/workflows/changelog.yml

@@ -35,4 +36,14 @@ .github/workflows/ci_devtests.yml

docs/io/vosi.rst
docs/mivot/annoter.rst
docs/mivot/annoter_tips.rst
docs/mivot/example.rst
docs/mivot/index.rst
docs/mivot/viewer.rst
docs/mivot/writer.rst
docs/mivot/_images/filterProfileService.png
docs/mivot/_images/mangoDataOrigin.png
docs/mivot/_images/mangoEpochPosition.png
docs/mivot/_images/mangoProperties.png
docs/mivot/_images/xtapdbSED.png
docs/mivot/_images/xtapdbXML.png
docs/registry/index.rst

@@ -196,2 +207,3 @@ docs/utils/index.rst

pyvo/mivot/__init__.py
pyvo/mivot/glossary.py
pyvo/mivot/version_checker.py

@@ -206,6 +218,10 @@ pyvo/mivot/features/__init__.py

pyvo/mivot/tests/__init__.py
pyvo/mivot/tests/conftest.py
pyvo/mivot/tests/test_annotation_seeker.py
pyvo/mivot/tests/test_header_mapper.py
pyvo/mivot/tests/test_mango_annoter.py
pyvo/mivot/tests/test_mivot_instance.py
pyvo/mivot/tests/test_mivot_instance_generation.py
pyvo/mivot/tests/test_mivot_viewer.py
pyvo/mivot/tests/test_mivot_writer.py
pyvo/mivot/tests/test_resource_seeker.py

@@ -217,6 +233,14 @@ pyvo/mivot/tests/test_sky_coord_builder.py

pyvo/mivot/tests/test_xml_viewer.py
pyvo/mivot/tests/data/filter_gaia_grp.xml
pyvo/mivot/tests/data/filter_gaia_grvs.xml
pyvo/mivot/tests/data/static_reference.xml
pyvo/mivot/tests/data/test.header_extraction.1.xml
pyvo/mivot/tests/data/test.header_extraction.2.xml
pyvo/mivot/tests/data/test.header_extraction.xml
pyvo/mivot/tests/data/test.mango_annoter.xml
pyvo/mivot/tests/data/test.mivot_viewer.first_instance.xml
pyvo/mivot/tests/data/test.mivot_viewer.no_mivot.xml
pyvo/mivot/tests/data/test.mivot_viewer.xml
pyvo/mivot/tests/data/test.photcal_SDSS.xml
pyvo/mivot/tests/data/test.photcal_error.xml
pyvo/mivot/tests/data/reference/annotation_seeker.0.1.xml

@@ -227,5 +251,11 @@ pyvo/mivot/tests/data/reference/annotation_seeker.0.2.xml

pyvo/mivot/tests/data/reference/instance_dmtypes.json
pyvo/mivot/tests/data/reference/mango_object.xml
pyvo/mivot/tests/data/reference/multiple_templates.xml
pyvo/mivot/tests/data/reference/static_reference_resolved.xml
pyvo/mivot/tests/data/reference/templates_models.json
pyvo/mivot/tests/data/reference/test_header_extraction.xml
pyvo/mivot/tests/data/reference/test_mivot_frames.xml
pyvo/mivot/tests/data/reference/test_mivot_photcal.xml
pyvo/mivot/tests/data/reference/test_mivot_writer.json
pyvo/mivot/tests/data/reference/test_mivot_writer.xml
pyvo/mivot/utils/__init__.py

@@ -243,2 +273,9 @@ pyvo/mivot/utils/dict_utils.py

pyvo/mivot/viewer/xml_viewer.py
pyvo/mivot/writer/__init__.py
pyvo/mivot/writer/annotations.py
pyvo/mivot/writer/header_mapper.py
pyvo/mivot/writer/instance.py
pyvo/mivot/writer/instances_from_models.py
pyvo/mivot/writer/mango_object.py
pyvo/mivot/writer/mivot-v1.xsd
pyvo/registry/__init__.py

@@ -245,0 +282,0 @@ pyvo/registry/regtap.py

@@ -25,3 +25,3 @@ import logging

def __init__(self):
super(AuthSession, self).__init__()
super().__init__()
self.credentials = CredentialStore()

@@ -28,0 +28,0 @@ self._auth_urls = AuthURLs()

@@ -103,6 +103,5 @@ import collections

# auth method.
for url, method in sorted(self.base_urls.items(),
yield from sorted(self.base_urls.items(),
key=sort_by_len,
reverse=True):
yield url, method
reverse=True)

@@ -109,0 +108,0 @@ def __repr__(self):

@@ -10,3 +10,3 @@ import logging

class CredentialStore(object):
class CredentialStore:
"""

@@ -13,0 +13,0 @@ The credential store takes user credentials, and uses them

@@ -148,3 +148,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

raise DALServiceError(
"No Adhoc Service with ivo-id {}!".format(ivo_id))
f"No Adhoc Service with ivo-id {ivo_id}!")

@@ -169,3 +169,3 @@ def get_adhocservice_by_id(self, id_):

raise DALServiceError(
"No Adhoc Service with service_def id {}!".format(id_))
f"No Adhoc Service with service_def id {id_}!")

@@ -773,3 +773,3 @@

semantics = set("#" + term for term in core_terms) | set(other_terms)
semantics = {"#" + term for term in core_terms} | set(other_terms)
for record in self:

@@ -776,0 +776,0 @@ if record.semantics in semantics:

@@ -53,3 +53,3 @@ """

def __repr__(self):
return "{}: {}".format(self._typeName(self), self._reason)
return f"{self._typeName(self)}: {self._reason}"

@@ -190,3 +190,3 @@ @property

if content_type and 'text/plain' in content_type:
message = '{} for {}'.format(response.text, url)
message = f'{response.text} for {url}'

@@ -197,3 +197,3 @@ # TODO votable handling

elif isinstance(exc, Exception):
return DALServiceError("{}: {}".format(cls._typeName(exc), str(exc)),
return DALServiceError(f"{cls._typeName(exc)}: {str(exc)}",
cause=exc, url=url)

@@ -200,0 +200,0 @@ else:

@@ -91,3 +91,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

if not mtype or len(mtype) > 2:
raise ValueError("Can't parse mimetype \"{}\"".format(full_type))
raise ValueError(f"Can't parse mimetype \"{full_type}\"")

@@ -94,0 +94,0 @@ if mtype[0] == 'text':

@@ -35,3 +35,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

raise KeyError('No param named {} defined'.format(keyword))
raise KeyError(f'No param named {keyword} defined')

@@ -336,3 +336,3 @@

if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg:
raise ValueError('Invalid circle radius: {}'.format(radius))
raise ValueError(f'Invalid circle radius: {radius}')
elif len(pos) == 3:

@@ -346,3 +346,3 @@ self._validate_ra(pos[0])

if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg:
raise ValueError('Invalid circle radius: {}'.format(radius))
raise ValueError(f'Invalid circle radius: {radius}')
elif len(pos) == 4:

@@ -375,3 +375,3 @@ ra_min = pos[0] if isinstance(pos[0], Quantity) else pos[0] * u.deg

if ra.to(u.deg).value < 0 or ra.to(u.deg).value > 360.0:
raise ValueError('Invalid ra: {}'.format(ra))
raise ValueError(f'Invalid ra: {ra}')

@@ -381,3 +381,3 @@ def _validate_dec(self, dec):

if dec.to(u.deg).value < -90.0 or dec.to(u.deg).value > 90.0:
raise ValueError('Invalid dec: {}'.format(dec))
raise ValueError(f'Invalid dec: {dec}')

@@ -432,3 +432,3 @@

return '{} {}'.format(low, high)
return f'{low} {high}'

@@ -462,3 +462,3 @@

))
return '{} {}'.format(min_time.mjd, max_time.mjd)
return f'{min_time.mjd} {max_time.mjd}'

@@ -465,0 +465,0 @@

@@ -133,3 +133,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
print('DAL Service at {}'.format(self.baseurl))
print(f'DAL Service at {self.baseurl}')

@@ -556,3 +556,3 @@

except KeyError:
raise KeyError("No such column: {}".format(name))
raise KeyError(f"No such column: {name}")

@@ -692,3 +692,3 @@ def getrecord(self, index):

except KeyError:
raise KeyError("No such column: {}".format(key))
raise KeyError(f"No such column: {key}")

@@ -896,3 +896,3 @@ def __iter__(self):

if not os.path.isdir(dir):
raise ValueError("{}: not a directory".format(dir))
raise ValueError(f"{dir}: not a directory")

@@ -912,3 +912,3 @@ if not base:

def mkpath(i):
return os.path.join(dir, "{}-{}.{}".format(base, i, ext))
return os.path.join(dir, f"{base}-{i}.{ext}")

@@ -923,3 +923,3 @@ if n > 0:

# never wrote a file of form, base-n.ext; try base.ext
path = os.path.join(dir, "{}.{}".format(base, ext))
path = os.path.join(dir, f"{base}.{ext}")
if not os.path.exists(path):

@@ -926,0 +926,0 @@ return path

@@ -23,2 +23,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from .adhoc import DatalinkResultsMixin, AxisParamMixin, SodaRecordMixin, DatalinkRecordMixin
from .exceptions import DALServiceError
from .params import IntervalQueryParam, StrQueryParam, EnumQueryParam

@@ -190,3 +191,3 @@ from .vosi import AvailabilityMixin, CapabilityMixin

# in pyvo. So pick any access url as long as it's not
if cap.standardid.lower() == SIA2_STANDARD_ID.lower():
if cap.standardid and cap.standardid.lower() == SIA2_STANDARD_ID.lower():
for interface in cap.interfaces:

@@ -202,2 +203,4 @@ if interface.accessurls and \

break
else:
raise DALServiceError("This URL does not seem to correspond to an SIA2 service.")

@@ -345,3 +348,3 @@ def search(self, pos=None, *, band=None, time=None, pol=None,

if isinstance(kw, tuple):
val = '{} {}'.format(kw[0], kw[1])
val = f'{kw[0]} {kw[1]}'
else:

@@ -348,0 +351,0 @@ val = str(kw)

@@ -154,3 +154,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

if self._tables is None:
tables_url = '{}/tables'.format(self.baseurl)
tables_url = f'{self.baseurl}/tables'

@@ -209,3 +209,3 @@ response = self._session.get(tables_url, params={"detail": "min"}, stream=True)

if self._examples is None:
examples_url = '{}/examples'.format(self.baseurl)
examples_url = f'{self.baseurl}/examples'

@@ -291,3 +291,3 @@ self._examples = self._parse_examples(examples_url)

self, query, *, language="ADQL", maxrec=None, uploads=None,
**keywords):
delete=True, **keywords):
"""

@@ -307,2 +307,4 @@ runs async query and returns its result

a mapping from table names to objects containing a votable
delete : bool
delete the job after fetching the results

@@ -332,6 +334,15 @@ Returns

job = job.run().wait()
job.raise_if_error()
try:
job.raise_if_error()
except DALQueryError:
if delete:
job.delete()
raise
result = job.fetch_result()
job.delete()
if delete:
job.delete()
return result

@@ -453,3 +464,3 @@

response = self._session.get('{}/async'.format(self.baseurl),
response = self._session.get(f'{self.baseurl}/async',
params=params,

@@ -519,3 +530,3 @@ stream=True)

headers = {'Content-Type': TABLE_DEF_FORMAT[format]}
response = self._session.put('{}/tables/{}'.format(self.baseurl, name),
response = self._session.put(f'{self.baseurl}/tables/{name}',
headers=headers,

@@ -542,3 +553,3 @@ data=definition)

response = self._session.delete(
'{}/tables/{}'.format(self.baseurl, name))
f'{self.baseurl}/tables/{name}')
response.raise_for_status()

@@ -572,3 +583,3 @@

response = self._session.post(
'{}/load/{}'.format(self.baseurl, name),
f'{self.baseurl}/load/{name}',
headers=headers,

@@ -597,3 +608,3 @@ data=source)

result = self._session.post('{}/table-update'.format(self.baseurl),
result = self._session.post(f'{self.baseurl}/table-update',
data={'table': table_name,

@@ -658,3 +669,3 @@ 'index': column_name,

def __init__(self, url, *, session=None):
def __init__(self, url, *, session=None, delete=True):
"""

@@ -667,5 +678,10 @@ initialize the job object with the given url and fetch remote values

the job url
session : object, optional
session to use for network requests
delete : bool, optional
whether to delete the job when exiting (default: True)
"""
self._url = url
self._session = use_session(session)
self._delete_on_exit = delete
self._update()

@@ -681,8 +697,11 @@

"""
Exits the context. The job is silently deleted.
Exits the context. Unless delete=False was set at initialization,
the job is deleted. Any deletion errors are silently ignored
to ensure proper context exit.
"""
try:
self.delete()
except Exception:
pass
if self._delete_on_exit:
try:
self.delete()
except DALServiceError:
pass

@@ -754,3 +773,3 @@ def _update(self, wait_for_statechange=False, timeout=10.):

response = self._session.post(
"{}/executionduration".format(self.url),
f"{self.url}/executionduration",
data={"EXECUTIONDURATION": str(value)})

@@ -788,3 +807,3 @@ response.raise_for_status()

response = self._session.post(
"{}/destruction".format(self.url),
f"{self.url}/destruction",
data={"DESTRUCTION": value.strftime(IVOA_DATETIME_FORMAT)})

@@ -828,3 +847,3 @@ response.raise_for_status()

response = self._session.post(
'{}/parameters'.format(self.url),
f'{self.url}/parameters',
data={"QUERY": query})

@@ -850,3 +869,3 @@ response.raise_for_status()

response = self._session.post(
'{}/parameters'.format(self.url),
f'{self.url}/parameters',
data={'UPLOAD': uploads.param()},

@@ -871,3 +890,3 @@ files=files

"""
The job result if exists
Returns the UWS result with id='result' if it exists, otherwise None.
"""

@@ -879,3 +898,3 @@ try:

return self._job.results[0]
return None
except IndexError:

@@ -897,3 +916,6 @@ return None

try:
uri = self.result.href
result = self.result
if result is None:
return None
uri = result.href
if not urlparse(uri).netloc:

@@ -925,3 +947,3 @@ uri = urljoin(self.url, uri)

response = self._session.post(
'{}/phase'.format(self.url), data={"PHASE": "RUN"})
f'{self.url}/phase', data={"PHASE": "RUN"})
response.raise_for_status()

@@ -939,3 +961,3 @@ except requests.RequestException as ex:

response = self._session.post(
'{}/phase'.format(self.url), data={"PHASE": "ABORT"})
f'{self.url}/phase', data={"PHASE": "ABORT"})
response.raise_for_status()

@@ -1024,2 +1046,9 @@ except requests.RequestException as ex:

"""
result_uri = self.result_uri
if result_uri is None:
self._update()
self.raise_if_error()
raise DALServiceError(reason="No result URI available",
url=self.url)
try:

@@ -1109,3 +1138,3 @@ response = self._session.get(self.result_uri, stream=True)

"""
return '{baseurl}/{mode}'.format(baseurl=self.baseurl, mode=self._mode)
return f'{self.baseurl}/{self._mode}'

@@ -1112,0 +1141,0 @@ def execute_stream(self, *, post=False):

@@ -7,2 +7,3 @@ #!/usr/bin/env python

from functools import partial
from pathlib import Path
import re

@@ -14,2 +15,3 @@ import requests_mock

from pyvo.dal.sia2 import search, SIA2Service, SIA2Query, SIAService, SIAQuery
from pyvo.dal.exceptions import DALServiceError

@@ -218,1 +220,39 @@ import astropy.units as u

assert SIA_PARAMETERS_DESC
def test_none_standardid_capability():
"""Test that SIA2Service handles capabilities with None standardID."""
# Mock a capabilities response with a None standardID
with requests_mock.Mocker() as m:
# Mock the capabilities endpoint
m.get('http://example.com/sia/capabilities',
content=b'''<?xml version="1.0" encoding="UTF-8"?>
<vosi:capabilities xmlns:vosi="http://www.ivoa.net/xml/VOSICapabilities/v1.0">
<capability>
<!-- This capability has no standardID attribute -->
<interface>
<accessURL use="full">http://example.com/sia/query</accessURL>
</interface>
</capability>
<capability standardID="ivo://ivoa.net/std/SIA#query-2.0">
<interface>
<accessURL use="full">http://example.com/sia/query</accessURL>
</interface>
</capability>
</vosi:capabilities>''')
# This should not raise an AttributeError
sia2_service = SIA2Service('http://example.com/sia')
# Basic verification that the service was created successfully
assert sia2_service is not None
assert sia2_service.query_ep is not None
def test_url_is_not_sia2():
# with capabilities from an other service type, we raise an error
with open(Path(__file__).parent / "data/tap/capabilities.xml", "rb") as f:
with requests_mock.Mocker() as mocker:
mocker.get("http://example.com/sia/capabilities", content=f.read())
with pytest.raises(DALServiceError,
match="This URL does not seem to correspond to an "
"SIA2 service."):
SIA2Service('http://example.com/sia')

@@ -10,2 +10,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from io import BytesIO, StringIO
from unittest.mock import Mock
from urllib.parse import parse_qsl

@@ -139,3 +140,6 @@ import tempfile

def validator(self, request):
pass
data = dict(parse_qsl(request.body))
if 'QUERY' in data:
if "select" not in data['QUERY'].casefold():
raise DALQueryError("Missing select")

@@ -204,3 +208,3 @@ def use(self, mocker):

context.headers['Location'] = (
'http://example.com/tap/async/{}'.format(newid))
f'http://example.com/tap/async/{newid}')

@@ -286,3 +290,3 @@ self._jobs[newid] = job

param.content = ';'.join([
'{}={}'.format(key, value) for key, value
f'{key}={value}' for key, value
in uploads1.items()

@@ -364,2 +368,8 @@ ])

@pytest.fixture()
def async_fixture_with_timeout(mocker):
mock_server = MockAsyncTAPServer()
yield from mock_server.use(mocker)
@pytest.fixture()
def tables(mocker):

@@ -541,8 +551,25 @@ def callback_tables(request, context):

@pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06")
def test_run_async(self):
def test_run_async(self, monkeypatch):
service = TAPService('http://example.com/tap')
results = service.run_async("SELECT * FROM ivoa.obscore")
mock_delete = Mock()
monkeypatch.setattr(AsyncTAPJob, "delete", mock_delete)
results = service.run_async("SELECT * FROM ivoa.obscore", delete=False)
_test_image_results(results)
# make sure that delete was not called
mock_delete.assert_not_called()
@pytest.mark.usefixtures('async_fixture')
@pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27")
@pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48")
@pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06")
def test_run_async_on_error(self, monkeypatch):
service = TAPService('http://example.com/tap')
mock_delete = Mock()
monkeypatch.setattr(AsyncTAPJob, "delete", mock_delete)
with pytest.raises(DALQueryError):
service.run_async("bad query", delete=True)
# make sure that the job is deleted even with a bad query
mock_delete.assert_called_once()
@pytest.mark.usefixtures('async_fixture')
def test_submit_job(self):

@@ -748,2 +775,99 @@ service = TAPService('http://example.com/tap')

@pytest.mark.usefixtures('async_fixture')
def test_job_no_result(self):
service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
with pytest.raises(DALServiceError) as excinfo:
job.fetch_result()
assert "No result URI available" in str(excinfo.value)
job.delete()
@pytest.mark.usefixtures('async_fixture')
def test_fetch_result_network_error(self):
service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
job.run()
job.wait()
status_response = '''<?xml version="1.0" encoding="UTF-8"?>
<uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<uws:jobId>1</uws:jobId>
<uws:phase>COMPLETED</uws:phase>
<uws:results>
<uws:result id="result" xsi:type="vot:VOTable"
href="http://example.com/tap/async/1/results/result"/>
</uws:results>
</uws:job>'''
with requests_mock.Mocker() as rm:
rm.get(f'http://example.com/tap/async/{job.job_id}',
text=status_response)
rm.get(
f'http://example.com/tap/async/{job.job_id}/results/result',
exc=requests.exceptions.ConnectTimeout
)
with pytest.raises(DALServiceError) as excinfo:
job.fetch_result()
assert "Unknown service error" in str(excinfo.value)
job.delete()
@pytest.mark.usefixtures('async_fixture')
def test_job_no_result_uri(self):
status_response = '''<?xml version="1.0" encoding="UTF-8"?>
<uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<uws:jobId>1</uws:jobId>
<uws:phase>COMPLETED</uws:phase>
<uws:results>
<uws:result id="diag" xlink:href="uws:executing:10"/>
</uws:results>
</uws:job>'''
service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
job.run()
job.wait()
with requests_mock.Mocker() as rm:
rm.get(f'http://example.com/tap/async/{job.job_id}',
text=status_response)
job._update()
with pytest.raises(DALServiceError) as excinfo:
job.fetch_result()
assert "No result URI available" in str(excinfo.value)
job.delete()
@pytest.mark.usefixtures('async_fixture')
def test_job_with_empty_error(self):
error_response = '''<?xml version="1.0" encoding="UTF-8"?>
<uws:job xmlns:uws="http://www.ivoa.net/xml/UWS/v1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<uws:jobId>1</uws:jobId>
<uws:phase>ERROR</uws:phase>
<uws:results/>
<uws:errorSummary>
<uws:message></uws:message>
</uws:errorSummary>
</uws:job>'''
service = TAPService('http://example.com/tap')
job = service.submit_job("SELECT * FROM ivoa.obscore")
job.run()
job.wait()
with requests_mock.Mocker() as rm:
rm.get(f'http://example.com/tap/async/{job.job_id}',
text=error_response)
job._update()
with pytest.raises(DALQueryError) as excinfo:
job.fetch_result()
assert "<No useful error from server>" in str(excinfo.value)
@pytest.mark.usefixtures('async_fixture')
def test_endpoint_503_with_retry_after(self):

@@ -837,3 +961,3 @@ service = TAPService('http://example.com/tap')

"ivo://ivoa.net/std/TAPRegExt#features-adqlgeo")
assert set(f.form for f in features) == {
assert {f.form for f in features} == {
'CENTROID', 'CONTAINS', 'COORD1', 'POLYGON',

@@ -840,0 +964,0 @@ 'INTERSECTS', 'COORD2', 'BOX', 'AREA', 'DISTANCE',

@@ -190,3 +190,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

tables_urls = [
'{}/tables'.format(self.baseurl),
f'{self.baseurl}/tables',
url_sibling(self.baseurl, 'tables')

@@ -245,3 +245,3 @@ ]

if not table.columns and not table.foreignkeys:
tables_url = '{}/{}'.format(self._endpoint_url, name)
tables_url = f'{self._endpoint_url}/{name}'
response = self._get_table_file(tables_url)

@@ -248,0 +248,0 @@

@@ -39,3 +39,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

# imports for type hints
from typing import Callable, Generator, List, Optional, Set, Tuple
from typing import Callable, Optional
from collections.abc import Generator
from astropy.units import quantity

@@ -70,2 +71,3 @@

"""
def __init__(self, res_rec):

@@ -83,3 +85,3 @@ self.res_rec = res_rec

@functools.lru_cache(maxsize=None)
@functools.cache
def obscore_column_names():

@@ -158,3 +160,3 @@ """returns the names of obscore columns.

def _clean_for(records: List[Queriable], ivoids_to_remove: Set[str]):
def _clean_for(records: list[Queriable], ivoids_to_remove: set[str]):
"""returns the Queriables in records the ivoids of which are

@@ -217,6 +219,6 @@ not in ivoids_to_remove.

self.already_queried, self.failed_services = 0, 0
self.results: List[obscore.ObsCoreMetadata] = []
self.results: list[obscore.ObsCoreMetadata] = []
self.watcher = watcher
self.log_messages: List[str] = []
self.known_access_urls: Set[str] = set()
self.log_messages: list[str] = []
self.known_access_urls: set[str] = set()

@@ -247,3 +249,3 @@ self._service_list_lock = threading.Lock()

def ids(recs):
return set(r.ivoid for r in recs)
return {r.ivoid for r in recs}

@@ -281,3 +283,3 @@ self.sia1_recs = _clean_for(self.sia1_recs,

collections_to_remove = set(r["ivoid"] for r in services_for)
collections_to_remove = {r["ivoid"] for r in services_for}
self.sia1_recs = _clean_for(self.sia1_recs, collections_to_remove)

@@ -305,8 +307,8 @@ self.sia2_recs = _clean_for(self.sia2_recs, collections_to_remove)

for rec in obscore_services:
new_style_access_urls |= set(
i.access_url for i in rec.list_interfaces("tap"))
new_style_access_urls |= {
i.access_url for i in rec.list_interfaces("tap")}
for tap_rec in tap_services_with_obscore:
access_urls = set(
i.baseurl for i in rec.list_services("tap"))
access_urls = {
i.baseurl for i in rec.list_services("tap")}
if new_style_access_urls.isdisjoint(access_urls):

@@ -345,7 +347,7 @@ obscore_services.append(obscore_services)

registry.Servicetype("sia"), *constraints)]
self._info("Found {} SIA1 service(s)".format(len(self.sia1_recs)))
self._info(f"Found {len(self.sia1_recs)} SIA1 service(s)")
self.sia2_recs = [Queriable(r) for r in registry.search(
registry.Servicetype("sia2"), *constraints)]
self._info("Found {} SIA2 service(s)".format(len(self.sia2_recs)))
self._info(f"Found {len(self.sia2_recs)} SIA2 service(s)")

@@ -448,3 +450,3 @@ self.obscore_recs = self._discover_obscore_services(*constraints)

self._info("Querying SIA1 {}...".format(rec.title))
self._info(f"Querying SIA1 {rec.title}...")
svc = rec.res_rec.get_service("sia", session=self.session, lax=True)

@@ -484,3 +486,3 @@ n_found = self._add_records(

"""
self._info("Querying SIA2 {}...".format(rec.title))
self._info(f"Querying SIA2 {rec.title}...")

@@ -519,3 +521,3 @@ svc = rec.res_rec.get_service("sia2", session=self.session, lax=True)

"""
self._info("Querying Obscore {}...".format(rec.title))
self._info(f"Querying Obscore {rec.title}...")
svc = rec.res_rec.get_service("tap", session=self.session, lax=True)

@@ -590,3 +592,3 @@

*,
space: Optional[Tuple[float, float, float]] = None,
space: Optional[tuple[float, float, float]] = None,
spectrum: Optional[quantity.Quantity] = None,

@@ -598,3 +600,3 @@ time: Optional[time.Time] = None,

services: Optional[registry.RegistryResults] = None)\
-> Tuple[List[obscore.ObsCoreMetadata], List[str]]:
-> tuple[list[obscore.ObsCoreMetadata], list[str]]:
"""returns a collection of ObsCoreMetadata-s matching certain constraints

@@ -601,0 +603,0 @@ and a list of log lines.

@@ -108,3 +108,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

assert set(queriable.search_kwargs) == set(["time"])
assert set(queriable.search_kwargs) == {"time"}
assert abs(queriable.search_kwargs["time"][0].utc.value

@@ -119,3 +119,3 @@ - 40872.54166667) < 1e-8

assert set(queriable.search_kwargs) == set(["time"])
assert set(queriable.search_kwargs) == {"time"}
assert abs(queriable.search_kwargs["time"][0].utc.value

@@ -122,0 +122,0 @@ - 40872.54166667) < 1e-8

@@ -30,3 +30,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

raise ValueError('Cannot parse datetime {}'.format(val))
raise ValueError(f'Cannot parse datetime {val}')

@@ -33,0 +33,0 @@

@@ -325,3 +325,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

return table
raise KeyError("No table with name {} found".format(name))
raise KeyError(f"No table with name {name} found")

@@ -328,0 +328,0 @@

# Licensed under a 3-clause BSD style license - see LICENSE.rst
# -*- coding: utf-8 -*-
"""

@@ -29,4 +28,4 @@ .. _warnings:

__all__ = ["VOSIWarning"]
__all__ += ["W{:0>2}".format(i) for i in range(1, 36)]
__all__ += ["E{:0>2}".format(i) for i in range(1, 10)]
__all__ += [f"W{i:0>2}" for i in range(1, 36)]
__all__ += [f"E{i:0>2}" for i in range(1, 10)]

@@ -33,0 +32,0 @@

@@ -42,3 +42,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
print("Datamodel {}".format(self.content))
print(f"Datamodel {self.content}")
print(indent(self.ivo_id, INDENT))

@@ -75,3 +75,3 @@ print()

"""
print('Output format {}'.format(self.mime))
print(f'Output format {self.mime}')

@@ -105,3 +105,3 @@ if self.aliases:

def __repr__(self):
return '<UploadMethod ivo-id="{}"/>'.format(self.ivo_id)
return f'<UploadMethod ivo-id="{self.ivo_id}"/>'

@@ -238,3 +238,3 @@ def describe(self):

def __repr__(self):
return '<Language>{}</Language>'.format(self.name)
return f'<Language>{self.name}</Language>'

@@ -245,3 +245,3 @@ def describe(self):

"""
print("Language {}".format(self.name))
print(f"Language {self.name}")

@@ -481,5 +481,5 @@ for languagefeaturelist in self.languagefeaturelists:

print("Time a job is kept (in seconds)")
print(indent("Default {}".format(self.retentionperiod.default), INDENT))
print(indent(f"Default {self.retentionperiod.default}", INDENT))
if self.retentionperiod.hard:
print(indent("Maximum {}".format(self.retentionperiod.hard), INDENT))
print(indent(f"Maximum {self.retentionperiod.hard}", INDENT))
print()

@@ -489,5 +489,5 @@

print("Maximal run time of a job")
print(indent("Default {}".format(self.executionduration.default), INDENT))
print(indent(f"Default {self.executionduration.default}", INDENT))
if self.executionduration.hard:
print(indent("Maximum {}".format(self.executionduration.hard), INDENT))
print(indent(f"Maximum {self.executionduration.hard}", INDENT))
print()

@@ -494,0 +494,0 @@

@@ -175,4 +175,3 @@ #!/usr/bin/env python

def test_udfs(self, parsed_minimal_tapregext):
assert parsed_minimal_tapregext[2].get_adql(
).languagefeaturelists == []
assert parsed_minimal_tapregext[2].get_adql().languagefeaturelists == []

@@ -179,0 +178,0 @@ def test_uploadmethods(self, parsed_minimal_tapregext):

@@ -496,3 +496,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

def __repr__(self):
return '<BaseParam name="{}"/>'.format(self.name)
return f'<BaseParam name="{self.name}"/>'

@@ -499,0 +499,0 @@ @xmlelement(plain=True, multiple_exc=W05)

@@ -242,3 +242,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

"""
print('Interface {}'.format(self._xsi_type))
print(f'Interface {self._xsi_type}')

@@ -374,3 +374,3 @@ accessurls = '\n'.join(

"""
print("Capability {}".format(self.standardid))
print(f"Capability {self.standardid}")
print()

@@ -377,0 +377,0 @@

@@ -1,2 +0,1 @@

# package entry point
from .viewer.mivot_viewer import MivotViewer
# Licensed under a 3-clause BSD style license - see LICENSE.rst

@@ -22,3 +22,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

RADIAL_VELOCITY = "radialVelocity"
EPOCH = "epoch"
EPOCH = "obsDate"
FRAME = "frame"

@@ -41,3 +41,3 @@ EQUINOX = "equinox"

class SkyCoordBuilder(object):
class SkyCoordBuilder:
'''

@@ -99,6 +99,14 @@ Utility generating SkyCoord instances from MIVOT annotations

-------
string
string or None
attribute value formatted as [scale]year
"""
scale = "J" if not besselian else "B"
# Process complex type "mango:DateTime
# only "year" representation are supported yet
if hk_field['dmtype'] == "mango:DateTime":
representation = hk_field['representation']['value']
timestamp = hk_field['dateTime']['value']
if representation == "year":
return f"{scale}{timestamp}"
return None
return (f"{scale}{hk_field['value']}" if hk_field["unit"] in ("yr", "year")

@@ -124,3 +132,3 @@ else hk_field["value"])

"""
coo_sys = self._mivot_instance_dict["coordSys"]
coo_sys = self._mivot_instance_dict["spaceSys"]["frame"]
equinox = None

@@ -127,0 +135,0 @@ frame = coo_sys["spaceRefFrame"]["value"].lower()

@@ -48,14 +48,6 @@ """

target = annotation_seeker.get_globals_instance_by_dmid(dmref)
found_in_global = True
if target is None and templates_ref is not None:
target = annotation_seeker.get_templates_instance_by_dmid(templates_ref, dmref)
found_in_global = False
if target is None:
raise MivotError(f"Cannot resolve reference={dmref}")
# Resolve static references recursively
if not found_in_global:
StaticReferenceResolver.resolve(annotation_seeker, templates_ref, ele)
else:
StaticReferenceResolver.resolve(annotation_seeker, None, ele)
# Set the reference role to the copied instance
target_copy = deepcopy(target)

@@ -62,0 +54,0 @@ # If the reference is within a collection: no role

"""
``seekers`` package contains utilities for retrieving
``seekers`` package contains utilities for retrieving
components of VOTales or of Mivot blocks
"""

@@ -1,222 +0,363 @@

<?xml version="1.0" encoding="UTF-8"?>
<VOTABLE version="1.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ivoa.net/xml/VOTable/v1.3"
xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/v1.3">
<RESOURCE ID="yCat_1329" name="I/329">
<DESCRIPTION>URAT1 Catalog (Zacharias+ 2015)</DESCRIPTION> <INFO name="ivoid" value="ivo://cds.vizier/i/329"> IVOID of underlying data collection </INFO>
<?xml version="1.0" encoding="utf-8"?>
<!-- Produced with astropy.io.votable version 6.0.0
http://www.astropy.org/ -->
<VOTABLE version="1.4" xmlns="http://www.ivoa.net/xml/VOTable/v1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ivoa.net/xml/VOTable/v1.3 http://www.ivoa.net/xml/VOTable/VOTable-1.4.xsd">
<RESOURCE ID="yCat_1329" type="results">
<DESCRIPTION>
URAT1 Catalog (Zacharias+ 2015)
</DESCRIPTION>
<COOSYS ID="H" system="ICRS"/>
<TIMESYS ID="time_1" refposition="UNKNOWN" timeorigin="0.000000" timescale="UNKNOWN"/>
<TABLE ID="I_329_urat1" name="I/329/urat1">
<DESCRIPTION>URAT1 catalog</DESCRIPTION>
<!-- RowName: -c=${$poseq}&-c.rs=0.004 -->
<!-- Definitions of GROUPs and FIELDs -->
<FIELD name="_r" ucd="pos.angDistance" datatype="double" width="8" precision="6" unit=""><!-- ucd="POS_ANG_DIST_GENERAL" -->
<DESCRIPTION>Distance from center (052.2670800+59.9402700)[ICRS], at Epoch of catalog (Epoch)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="URAT1" ucd="meta.id;meta.main" datatype="char" arraysize="10*"><!-- ucd="meta.id;meta.main" -->
<DESCRIPTION>URAT1 recommended identifier (ZZZ-NNNNNN) (13) [datatype=char]</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="RAICRS" ucd="pos.eq.ra;meta.main" ref="H" datatype="double" width="11" precision="7" unit="deg"><!-- ucd="pos.eq.ra;meta.main" -->
<DESCRIPTION>Right ascension on ICRS, at "Epoch" (1)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="DEICRS" ucd="pos.eq.dec;meta.main" ref="H" datatype="double" width="11" precision="7" unit="deg"><!-- ucd="pos.eq.dec;meta.main" -->
<DESCRIPTION>Declination on ICRS, at "Epoch" (1)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="sigs" ucd="stat.error" datatype="short" width="3" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>Position error per coordinate, from scatter (2)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="sigm" ucd="stat.error" datatype="short" width="3" unit="mas"><!-- ucd="ERROR" -->
<DESCRIPTION>Position error per coordinate, from model (2)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Ns" ucd="meta.number" datatype="short" width="2"><!-- ucd="NUMBER" -->
<DESCRIPTION>(nst) Total number of sets the star is in (3)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Nu" ucd="meta.number" datatype="short" width="2"><!-- ucd="NUMBER" -->
<DESCRIPTION>(nsu) Number of sets used for mean position (3)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Epoch" ucd="time.epoch;obs;stat.mean" ref="H" ID="_tab1_8" datatype="double" width="8" precision="3" unit="yr"><!-- ucd="TIME_EPOCH" -->
<DESCRIPTION>(epoc) Mean URAT observation epoch (1)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="f.mag" ucd="phot.mag;em.opt.R" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_MAG_OPTICAL" -->
<DESCRIPTION>?(mmag) mean URAT model fit magnitude (4)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_f.mag" ucd="stat.error" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(sigp) URAT photometry error (5)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="Nm" ucd="meta.number" datatype="short" width="2"><!-- ucd="NUMBER" -->
<DESCRIPTION>(nsm) Number of sets used for URAT magnitude (3)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="r" ucd="meta.code" datatype="unsignedByte" width="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>(ref) largest reference star flag (6)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Nit" ucd="meta.number" datatype="short" width="3"><!-- ucd="NUMBER" -->
<DESCRIPTION>(nit) Total number of images (observations)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Niu" ucd="meta.number" datatype="short" width="3"><!-- ucd="NUMBER" -->
<DESCRIPTION>(niu) Number of images used for mean position</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Ngt" ucd="meta.number" datatype="short" width="3"><!-- ucd="NUMBER" -->
<DESCRIPTION>(ngt) Total number of 1st order grating observations</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Ngu" ucd="meta.number" datatype="short" width="3"><!-- ucd="NUMBER" -->
<DESCRIPTION>(ngu) Number of 1st order grating positions used</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="pmRA" ucd="pos.pm;pos.eq.ra" ref="H" datatype="float" width="6" precision="1" unit="mas/yr"><!-- ucd="POS_EQ_PMRA" -->
<DESCRIPTION>?(pmr) Proper motion RA*cosDec (from 2MASS) (7)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="pmDE" ucd="pos.pm;pos.eq.dec" ref="H" datatype="float" width="6" precision="1" unit="mas/yr"><!-- ucd="POS_EQ_PMDEC" -->
<DESCRIPTION>?(pmd) Proper motion in Declination (7)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_pm" ucd="stat.error" datatype="float" width="4" precision="1" unit="mas/yr"><!-- ucd="ERROR" -->
<DESCRIPTION>?(pme) Proper motion error per coordinate (8)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="mf2" ucd="meta.code" datatype="short" width="2"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[1/11] Match flag URAT with 2MASS (9)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="mfa" ucd="meta.code" datatype="short" width="2"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[1/11] Match flag URAT with APASS (9)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="G" ucd="meta.code" datatype="char" arraysize="1"><!-- ucd="CODE_MISC" -->
<DESCRIPTION>[-] "-" if there is no match with GSC2.4 (14)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="2Mkey" ID="_2Mkey" ucd="meta.id" datatype="long" width="10"><!-- ucd="ID_NUMBER" -->
<DESCRIPTION>?(id2) unique 2MASS star identification number</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="-9223372036854775808" />
</FIELD>
<FIELD name="Jmag" ucd="phot.mag;em.IR.J" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_JHN_J" -->
<DESCRIPTION>?(jmag) 2MASS J-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Jmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(ejmag) Error on Jmag</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="q_Jmag" ucd="meta.code.qual" datatype="char" arraysize="2"><!-- ucd="CODE_QUALITY" -->
<DESCRIPTION>[0,58]? J-band quality-confusion flag (10)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Hmag" ucd="phot.mag;em.IR.H" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_JHN_H" -->
<DESCRIPTION>?(hmag) 2MASS H-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Hmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(ehmag) Error on H-band magnitude (10)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="q_Hmag" ucd="meta.code.qual" datatype="char" arraysize="2"><!-- ucd="CODE_QUALITY" -->
<DESCRIPTION>[0,58]? H-band quality-confusion flag (10)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Kmag" ucd="phot.mag;em.IR.K" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_JHN_K" -->
<DESCRIPTION>?(kmag) 2MASS Ks-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Kmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(ekmag) Error on Ks-band magnitude (10)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="q_Kmag" ucd="meta.code.qual" datatype="char" arraysize="2"><!-- ucd="CODE_QUALITY" -->
<DESCRIPTION>[0,58]? Ks-band quality-confusion flag (10)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Nn" ucd="meta.number" datatype="short" width="3"><!-- ucd="NUMBER" -->
<DESCRIPTION>(ann) Number of APASS observation nights (12)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="No" ucd="meta.number" datatype="short" width="3"><!-- ucd="NUMBER" -->
<DESCRIPTION>(ano) Number of APASS observations (12)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
</FIELD>
<FIELD name="Bmag" ucd="phot.mag;em.opt.B" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_MAG_B" -->
<DESCRIPTION>?(abm) APASS B-band magnitude (11)</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Bmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(ebm) Error on Bmag</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="Vmag" ucd="phot.mag;em.opt.V" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_MAG_V" -->
<DESCRIPTION>?(avm) APASS V-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_Vmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(evm) Error on Vmag</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="gmag" ucd="phot.mag;em.opt.B" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_SDSS_G" -->
<DESCRIPTION>?(agm) APASS g-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_gmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(egm) Error on gmag</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="rmag" ucd="phot.mag;em.opt.R" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_SDSS_R" -->
<DESCRIPTION>?(arm) APASS r-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_rmag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(erm) Error on rmag</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="imag" ucd="phot.mag;em.opt.I" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="PHOT_SDSS_I" -->
<DESCRIPTION>?(aim) APASS i-band magnitude</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<FIELD name="e_imag" ucd="stat.error;phot.mag" datatype="float" width="6" precision="3" unit="mag"><!-- ucd="ERROR" -->
<DESCRIPTION>?(eim) Error on imag</DESCRIPTION>
<!-- ++++ What is 'M' in edition ?? -->
<VALUES null="NaN" />
</FIELD>
<DATA><TABLEDATA>
<TR><TD>0.049402</TD><TD>750-146023</TD><TD>052.2340018</TD><TD>+59.8937333</TD><TD>9</TD><TD>6</TD><TD>13</TD><TD>13</TD><TD>2013.418</TD><TD>15.340</TD><TD>0.013</TD><TD>13</TD><TD>9</TD><TD>74</TD><TD>74</TD><TD>0</TD><TD>0</TD><TD>1.5</TD><TD>-12.3</TD><TD>5.9</TD><TD>1</TD><TD>5</TD><TD></TD><TD>758808681</TD><TD>13.713</TD><TD>0.028</TD><TD>5</TD><TD>13.340</TD><TD>0.034</TD><TD>5</TD><TD>13.101</TD><TD>0.034</TD><TD>5</TD><TD>1</TD><TD>4</TD><TD>17.632</TD><TD>0.204</TD><TD>16.164</TD><TD>0.001</TD><TD>16.690</TD><TD>0.001</TD><TD>15.750</TD><TD>0.001</TD><TD></TD><TD></TD></TR>
</TABLEDATA></DATA>
</TABLE>
</RESOURCE>
<TIMESYS ID="time_1" refposition="UNKNOWN" timeorigin="0.0" timescale="UNKNOWN"/>
<INFO ID="ivoid" name="ivoid" value="ivo://cds.vizier/i/329">IVOID of underlying data collection</INFO>
<TABLE ID="I_329_urat1" name="I/329/urat1" nrows="1">
<DESCRIPTION>
URAT1 catalog
</DESCRIPTION>
<FIELD ID="_r" datatype="double" name="_r" precision="6" ucd="pos.angDistance" unit="---" width="8">
<DESCRIPTION>
Distance from center (052.2670800+59.9402700)[ICRS], at Epoch of
catalog (Epoch)
</DESCRIPTION>
</FIELD>
<FIELD ID="URAT1" arraysize="10" datatype="char" name="URAT1" ucd="meta.id;meta.main">
<DESCRIPTION>
URAT1 recommended identifier (ZZZ-NNNNNN) (13) [datatype=char]
</DESCRIPTION>
</FIELD>
<FIELD ID="RAICRS" datatype="double" name="RAICRS" precision="7" ref="H" ucd="pos.eq.ra;meta.main" unit="deg" width="11">
<DESCRIPTION>
Right ascension on ICRS, at "Epoch" (1)
</DESCRIPTION>
</FIELD>
<FIELD ID="DEICRS" datatype="double" name="DEICRS" precision="7" ref="H" ucd="pos.eq.dec;meta.main" unit="deg" width="11">
<DESCRIPTION>
Declination on ICRS, at "Epoch" (1)
</DESCRIPTION>
</FIELD>
<FIELD ID="sigs" datatype="short" name="sigs" ucd="stat.error" unit="mas" width="3">
<DESCRIPTION>
Position error per coordinate, from scatter (2)
</DESCRIPTION>
</FIELD>
<FIELD ID="sigm" datatype="short" name="sigm" ucd="stat.error" unit="mas" width="3">
<DESCRIPTION>
Position error per coordinate, from model (2)
</DESCRIPTION>
</FIELD>
<FIELD ID="Ns" datatype="short" name="Ns" ucd="meta.number" width="2">
<DESCRIPTION>
(nst) Total number of sets the star is in (3)
</DESCRIPTION>
</FIELD>
<FIELD ID="Nu" datatype="short" name="Nu" ucd="meta.number" width="2">
<DESCRIPTION>
(nsu) Number of sets used for mean position (3)
</DESCRIPTION>
</FIELD>
<FIELD ID="_tab1_8" datatype="double" name="Epoch" precision="3" ref="H" ucd="time.epoch;obs;stat.mean" unit="yr" width="8">
<DESCRIPTION>
(epoc) Mean URAT observation epoch (1)
</DESCRIPTION>
</FIELD>
<FIELD ID="f.mag" datatype="float" name="f.mag" precision="3" ucd="phot.mag;em.opt.R" unit="mag" width="6">
<DESCRIPTION>
?(mmag) mean URAT model fit magnitude (4)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_f.mag" datatype="float" name="e_f.mag" precision="3" ucd="stat.error" unit="mag" width="6">
<DESCRIPTION>
?(sigp) URAT photometry error (5)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="Nm" datatype="short" name="Nm" ucd="meta.number" width="2">
<DESCRIPTION>
(nsm) Number of sets used for URAT magnitude (3)
</DESCRIPTION>
</FIELD>
<FIELD ID="r" datatype="unsignedByte" name="r" ucd="meta.code" width="1">
<DESCRIPTION>
(ref) largest reference star flag (6)
</DESCRIPTION>
</FIELD>
<FIELD ID="Nit" datatype="short" name="Nit" ucd="meta.number" width="3">
<DESCRIPTION>
(nit) Total number of images (observations)
</DESCRIPTION>
</FIELD>
<FIELD ID="Niu" datatype="short" name="Niu" ucd="meta.number" width="3">
<DESCRIPTION>
(niu) Number of images used for mean position
</DESCRIPTION>
</FIELD>
<FIELD ID="Ngt" datatype="short" name="Ngt" ucd="meta.number" width="3">
<DESCRIPTION>
(ngt) Total number of 1st order grating observations
</DESCRIPTION>
</FIELD>
<FIELD ID="Ngu" datatype="short" name="Ngu" ucd="meta.number" width="3">
<DESCRIPTION>
(ngu) Number of 1st order grating positions used
</DESCRIPTION>
</FIELD>
<FIELD ID="pmRA" datatype="float" name="pmRA" precision="1" ref="H" ucd="pos.pm;pos.eq.ra" unit="mas.yr-1" width="6">
<DESCRIPTION>
?(pmr) Proper motion RA*cosDec (from 2MASS) (7)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="pmDE" datatype="float" name="pmDE" precision="1" ref="H" ucd="pos.pm;pos.eq.dec" unit="mas.yr-1" width="6">
<DESCRIPTION>
?(pmd) Proper motion in Declination (7)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_pm" datatype="float" name="e_pm" precision="1" ucd="stat.error" unit="mas.yr-1" width="4">
<DESCRIPTION>
?(pme) Proper motion error per coordinate (8)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="mf2" datatype="short" name="mf2" ucd="meta.code" width="2">
<DESCRIPTION>
[1/11] Match flag URAT with 2MASS (9)
</DESCRIPTION>
</FIELD>
<FIELD ID="mfa" datatype="short" name="mfa" ucd="meta.code" width="2">
<DESCRIPTION>
[1/11] Match flag URAT with APASS (9)
</DESCRIPTION>
</FIELD>
<FIELD ID="G" arraysize="1" datatype="char" name="G" ucd="meta.code">
<DESCRIPTION>
[-] "-" if there is no match with GSC2.4 (14)
</DESCRIPTION>
</FIELD>
<FIELD ID="_2Mkey" datatype="long" name="2Mkey" ucd="meta.id" width="10">
<DESCRIPTION>
?(id2) unique 2MASS star identification number
</DESCRIPTION>
<VALUES null="-9223372036854775808"/>
</FIELD>
<FIELD ID="Jmag" datatype="float" name="Jmag" precision="3" ucd="phot.mag;em.IR.J" unit="mag" width="6">
<DESCRIPTION>
?(jmag) 2MASS J-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_Jmag" datatype="float" name="e_Jmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(ejmag) Error on Jmag
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="q_Jmag" arraysize="2" datatype="char" name="q_Jmag" ucd="meta.code.qual">
<DESCRIPTION>
[0,58]? J-band quality-confusion flag (10)
</DESCRIPTION>
</FIELD>
<FIELD ID="Hmag" datatype="float" name="Hmag" precision="3" ucd="phot.mag;em.IR.H" unit="mag" width="6">
<DESCRIPTION>
?(hmag) 2MASS H-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_Hmag" datatype="float" name="e_Hmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(ehmag) Error on H-band magnitude (10)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="q_Hmag" arraysize="2" datatype="char" name="q_Hmag" ucd="meta.code.qual">
<DESCRIPTION>
[0,58]? H-band quality-confusion flag (10)
</DESCRIPTION>
</FIELD>
<FIELD ID="Kmag" datatype="float" name="Kmag" precision="3" ucd="phot.mag;em.IR.K" unit="mag" width="6">
<DESCRIPTION>
?(kmag) 2MASS Ks-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_Kmag" datatype="float" name="e_Kmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(ekmag) Error on Ks-band magnitude (10)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="q_Kmag" arraysize="2" datatype="char" name="q_Kmag" ucd="meta.code.qual">
<DESCRIPTION>
[0,58]? Ks-band quality-confusion flag (10)
</DESCRIPTION>
</FIELD>
<FIELD ID="Nn" datatype="short" name="Nn" ucd="meta.number" width="3">
<DESCRIPTION>
(ann) Number of APASS observation nights (12)
</DESCRIPTION>
</FIELD>
<FIELD ID="No" datatype="short" name="No" ucd="meta.number" width="3">
<DESCRIPTION>
(ano) Number of APASS observations (12)
</DESCRIPTION>
</FIELD>
<FIELD ID="Bmag" datatype="float" name="Bmag" precision="3" ucd="phot.mag;em.opt.B" unit="mag" width="6">
<DESCRIPTION>
?(abm) APASS B-band magnitude (11)
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_Bmag" datatype="float" name="e_Bmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(ebm) Error on Bmag
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="Vmag" datatype="float" name="Vmag" precision="3" ucd="phot.mag;em.opt.V" unit="mag" width="6">
<DESCRIPTION>
?(avm) APASS V-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_Vmag" datatype="float" name="e_Vmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(evm) Error on Vmag
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="gmag" datatype="float" name="gmag" precision="3" ucd="phot.mag;em.opt.B" unit="mag" width="6">
<DESCRIPTION>
?(agm) APASS g-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_gmag" datatype="float" name="e_gmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(egm) Error on gmag
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="rmag" datatype="float" name="rmag" precision="3" ucd="phot.mag;em.opt.R" unit="mag" width="6">
<DESCRIPTION>
?(arm) APASS r-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_rmag" datatype="float" name="e_rmag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(erm) Error on rmag
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="imag" datatype="float" name="imag" precision="3" ucd="phot.mag;em.opt.I" unit="mag" width="6">
<DESCRIPTION>
?(aim) APASS i-band magnitude
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<FIELD ID="e_imag" datatype="float" name="e_imag" precision="3" ucd="stat.error;phot.mag" unit="mag" width="6">
<DESCRIPTION>
?(eim) Error on imag
</DESCRIPTION>
<VALUES null="nan"/>
</FIELD>
<DATA>
<TABLEDATA>
<TR>
<TD>0.049402</TD>
<TD>750-146023</TD>
<TD> 52.2340018</TD>
<TD> 59.8937333</TD>
<TD>9</TD>
<TD>6</TD>
<TD>13</TD>
<TD>13</TD>
<TD>2013.418</TD>
<TD>15.340</TD>
<TD> 0.013</TD>
<TD>13</TD>
<TD>9</TD>
<TD>74</TD>
<TD>74</TD>
<TD>0</TD>
<TD>0</TD>
<TD> 1.5</TD>
<TD> -12.3</TD>
<TD> 5.9</TD>
<TD>1</TD>
<TD>5</TD>
<TD/>
<TD>758808681</TD>
<TD>13.713</TD>
<TD> 0.028</TD>
<TD>5</TD>
<TD>13.340</TD>
<TD> 0.034</TD>
<TD>5</TD>
<TD>13.101</TD>
<TD> 0.034</TD>
<TD>5</TD>
<TD>1</TD>
<TD>4</TD>
<TD>17.632</TD>
<TD> 0.204</TD>
<TD>16.164</TD>
<TD> 0.001</TD>
<TD>16.690</TD>
<TD> 0.001</TD>
<TD>15.750</TD>
<TD> 0.001</TD>
<TD/>
<TD/>
</TR> <TR>
<TD>0.049402</TD>
<TD>750-146023</TD>
<TD> 58.2340018</TD>
<TD> 69.8937333</TD>
<TD>9</TD>
<TD>6</TD>
<TD>13</TD>
<TD>13</TD>
<TD>2013.418</TD>
<TD>15.340</TD>
<TD> 0.013</TD>
<TD>13</TD>
<TD>9</TD>
<TD>74</TD>
<TD>74</TD>
<TD>0</TD>
<TD>0</TD>
<TD> 1.5</TD>
<TD> -12.3</TD>
<TD> 5.9</TD>
<TD>1</TD>
<TD>5</TD>
<TD/>
<TD>758808681</TD>
<TD>13.713</TD>
<TD> 0.028</TD>
<TD>5</TD>
<TD>13.340</TD>
<TD> 0.034</TD>
<TD>5</TD>
<TD>13.101</TD>
<TD> 0.034</TD>
<TD>5</TD>
<TD>1</TD>
<TD>4</TD>
<TD>17.632</TD>
<TD> 0.204</TD>
<TD>16.164</TD>
<TD> 0.001</TD>
<TD>16.690</TD>
<TD> 0.001</TD>
<TD>15.750</TD>
<TD> 0.001</TD>
<TD/>
<TD/>
</TR>
</TABLEDATA>
</DATA>
</TABLE>
</RESOURCE>
</VOTABLE>

@@ -14,3 +14,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from . import XMLOutputChecker

@@ -17,0 +17,0 @@

@@ -9,3 +9,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from pyvo.mivot.utils.mivot_utils import MivotUtils

@@ -28,3 +28,3 @@

m_viewer_votable_test = MivotViewer(votable_path=votable_test)
m_viewer_votable_test.next()
m_viewer_votable_test.next_row_view()
mivot_instance = m_viewer_votable_test.dm_instance

@@ -91,3 +91,7 @@ xml_votable_test = m_viewer_votable_test.xml_view

assert value == MivotInstance_attribute.value
elif child.tag.startswith("REFERENCE"):
# Viewer not in resolve_ref mode: REFRENCEs are not filtered
pass
else:
print(child.tag)
assert False

@@ -108,3 +112,3 @@

m_viewer_votable_test = MivotViewer(votable_path=votable_test)
m_viewer_votable_test.next()
m_viewer_votable_test.next_row_view()
mivot_instance = m_viewer_votable_test.dm_instance

@@ -111,0 +115,0 @@ _dict = MivotUtils.xml_to_dict(m_viewer_votable_test.xml_viewer.view)

@@ -14,3 +14,3 @@ '''

from pyvo.mivot.utils.mivot_utils import MivotUtils
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer

@@ -36,2 +36,3 @@ fake_hk_dict = {

"longitude": {
"dmtype": "RealQuantity",
"value": 52.2340018,

@@ -41,2 +42,3 @@ "unit": "deg",

"latitude": {
"dmtype": "RealQuantity",
"value": 59.8937333,

@@ -56,3 +58,3 @@ "unit": "deg",

"dmtype": "cube:Observable",
"dependent": {"value": True},
"dependent": {"dmtype": "ivoa:boolean", "value": True},
"measure": {

@@ -64,3 +66,3 @@ "dmrole": "cube:MeasurementAxis.measure",

"dmtype": "coords:MJD",
"date": {"value": 1705.9437360200984},
"date": {"dmtype": "ivoa:real", "value": 1705.9437360200984},
},

@@ -71,3 +73,3 @@ },

"dmtype": "cube:Observable",
"dependent": {"value": True},
"dependent": {"dmtype": "ivoa:boolean", "value": True},
"measure": {

@@ -79,3 +81,3 @@ "dmrole": "cube:MeasurementAxis.measure",

"dmtype": "coords:PhysicalCoordinate",
"cval": {"value": 15.216575},
"cval": {"dmtype": "ivoa:RealQuantity", "value": 15.216575},
},

@@ -86,3 +88,3 @@ },

"dmtype": "cube:Observable",
"dependent": {"value": True},
"dependent": {"dmtype": "ivoa:boolean", "value": True},
"measure": {

@@ -94,3 +96,3 @@ "dmrole": "cube:MeasurementAxis.measure",

"dmtype": "coords:PhysicalCoordinate",
"cval": {"value": 15442.456},
"cval": {"dmtype": "ivoa:RealQuantity", "value": 15442.456},
},

@@ -103,3 +105,3 @@ "error": {

"dmtype": "meas:Symmetrical",
"radius": {"value": 44.15126},
"radius": {"dmtype": "ivoa:RealQuantity", "value": 44.15126},
},

@@ -106,0 +108,0 @@ },

@@ -13,3 +13,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from astropy import version as astropy_version

@@ -16,0 +16,0 @@

@@ -42,3 +42,3 @@ '''

},
"epoch": {
"obsDate": {
"dmtype": "ivoa:RealQuantity",

@@ -49,11 +49,13 @@ "value": 1991.25,

},
"coordSys": {
"spaceSys": {
"dmtype": "coords:SpaceSys",
"dmid": "SpaceFrame_ICRS",
"dmrole": "coords:Coordinate.coordSys",
"spaceRefFrame": {
"frame": {
"dmtype": "coords:SpaceFrame",
"value": "ICRS",
"unit": None,
"ref": None,
"dmrole": "coords:PhysicalCoordSys.frame",
"spaceRefFrame": {
"dmtype": "ivoa:string",
"value": "ICRS"
},
},

@@ -95,3 +97,3 @@ },

},
"epoch": {
"obsDate": {
"dmtype": "ivoa:RealQuantity",

@@ -102,17 +104,19 @@ "value": 1991.25,

},
"coordSys": {
"spaceSys": {
"dmtype": "coords:SpaceSys",
"dmid": "SpaceFrame_ICRS",
"dmrole": "coords:Coordinate.coordSys",
"spaceRefFrame": {
"dmtype": "coords:SpaceFrame.spaceRefFrame",
"value": "FK5",
"unit": None,
"ref": None,
},
"equinox": {
"dmtype": "coords:SpaceFrame.equinox",
"value": "2012",
"unit": "yr",
},
"frame": {
"dmtype": "coords:SpaceFrame",
"dmrole": "coords:PhysicalCoordSys.frame",
"spaceRefFrame": {
"dmtype": "ivoa:string",
"value": "FK5"
},
"equinox": {
"dmtype": "coords:SpaceFrame.equinox",
"value": "2012",
"unit": "yr",
}
}
},

@@ -154,3 +158,3 @@ }

},
"epoch": {
"obsDate": {
"dmtype": "ivoa:RealQuantity",

@@ -205,3 +209,3 @@ "value": 1991.25,

vizier_dict["coordSys"]["spaceRefFrame"]["value"] = "Galactic"
vizier_dict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "Galactic"
mivot_instance = MivotInstance(**vizier_dict)

@@ -213,3 +217,3 @@ scoo = mivot_instance.get_SkyCoord()

vizier_dict["coordSys"]["spaceRefFrame"]["value"] = "QWERTY"
vizier_dict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "QWERTY"
mivot_instance = MivotInstance(**vizier_dict)

@@ -235,3 +239,3 @@ scoo = mivot_instance.get_SkyCoord()

vizier_equin_dict["coordSys"]["spaceRefFrame"]["value"] = "FK4"
vizier_equin_dict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "FK4"
mivot_instance = MivotInstance(**vizier_equin_dict)

@@ -238,0 +242,0 @@ scoo = mivot_instance.get_SkyCoord()

@@ -10,3 +10,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from . import XMLOutputChecker

@@ -13,0 +13,0 @@

@@ -15,6 +15,6 @@ """

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from astropy.io.votable import parse
from pyvo.mivot.utils.dict_utils import DictUtils
ref_ra = [

@@ -66,3 +66,3 @@ 0.04827189,

def vizier_url():
return "https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch/I/239/hip_main"
return "https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main?RA=0&DEC=0&;SR=0.5"

@@ -104,3 +104,3 @@

"""
mivot_viewer = MivotViewer(path_to_votable)
mivot_viewer = MivotViewer(path_to_votable, resolve_ref=True)
mivot_instance = mivot_viewer.dm_instance

@@ -113,3 +113,3 @@ assert mivot_instance.dmtype == "mango:EpochPosition"

pmdec = []
while mivot_viewer.next():
while mivot_viewer.next_row_view():
ra.append(mivot_instance.longitude.value)

@@ -137,3 +137,3 @@ dec.append(mivot_instance.latitude.value)

table = votable.resources[0].tables[0]
mivot_viewer = MivotViewer(votable)
mivot_viewer = MivotViewer(votable, resolve_ref=True)

@@ -197,3 +197,3 @@ mivot_instance = mivot_viewer.dm_instance

mivot_object = mivot_viewer.dm_instance
while mivot_viewer.next():
while mivot_viewer.next_row_view():
read_ra.append(mivot_object.longitude.value)

@@ -214,8 +214,7 @@ read_dec.append(mivot_object.latitude.value)

with MivotViewer(path_to_votable) as mivot_viewer:
with MivotViewer(path_to_votable, resolve_ref=True) as mivot_viewer:
mivot_object = mivot_viewer.dm_instance
# let's focus on the last data row
while mivot_viewer.next():
# let"s focus on the last data row
while mivot_viewer.next_row_view():
pass
DictUtils.print_pretty_json(mivot_object.to_hk_dict())

@@ -225,7 +224,7 @@ # check the slim (user friendly) dictionary

"dmtype": "mango:EpochPosition",
"longitude": {"value": 359.94372764, "unit": "deg"},
"latitude": {"value": -0.28005255, "unit": "deg"},
"pmLongitude": {"value": -5.14, "unit": "mas/yr"},
"pmLatitude": {"value": -25.43, "unit": "mas/yr"},
"epoch": {"value": 1991.25, "unit": "year"},
"longitude": {"dmtype": "ivoa:RealQuantity", "value": 359.94372764, "unit": "deg"},
"latitude": {"dmtype": "ivoa:RealQuantity", "value": -0.28005255, "unit": "deg"},
"pmLongitude": {"dmtype": "ivoa:RealQuantity", "value": -5.14, "unit": "mas/yr"},
"pmLatitude": {"dmtype": "ivoa:RealQuantity", "value": -25.43, "unit": "mas/yr"},
"epoch": {"dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "year"},
"coordSys": {

@@ -235,3 +234,3 @@ "dmtype": "coords:SpaceSys",

"dmrole": "coords:Coordinate.coordSys",
"spaceRefFrame": {"value": "ICRS"},
"spaceRefFrame": {"dmtype": "coords:SpaceFrame", "value": "ICRS"},
},

@@ -293,17 +292,17 @@ }

with MivotViewer(path_to_full_mapped_votable) as mivot_viewer:
with MivotViewer(path_to_full_mapped_votable, resolve_ref=True) as mivot_viewer:
mivot_object = mivot_viewer.dm_instance
# let's focus on the second data row
while mivot_viewer.next():
# let"s focus on the second data row
while mivot_viewer.next_row_view():
# check the slim (user friendly) dictionary
assert mivot_object.to_dict() == {
"dmtype": "mango:EpochPosition",
"longitude": {"value": 307.79115807079, "unit": "deg"},
"latitude": {"value": 20.43108005561, "unit": "deg"},
"parallax": {"value": 0.4319, "unit": "mas"},
"radialVelocity": {"value": None, "unit": "km/s"},
"pmLongitude": {"value": -2.557, "unit": "mas/yr"},
"pmLatitude": {"value": -5.482, "unit": "mas/yr"},
"epoch": {"value": "2016.5"},
"pmCosDeltApplied": {"value": True},
"longitude": {"dmtype": "ivoa:RealQuantity", "value": 307.79115807079, "unit": "deg"},
"latitude": {"dmtype": "ivoa:RealQuantity", "value": 20.43108005561, "unit": "deg"},
"parallax": {"dmtype": "ivoa:RealQuantity", "value": 0.4319, "unit": "mas"},
"radialVelocity": {"dmtype": "ivoa:RealQuantity", "value": None, "unit": "km/s"},
"pmLongitude": {"dmtype": "ivoa:RealQuantity", "value": -2.557, "unit": "mas/yr"},
"pmLatitude": {"dmtype": "ivoa:RealQuantity", "value": -5.482, "unit": "mas/yr"},
"epoch": {"dmtype": "coords:Epoch", "value": "2016.5"},
"pmCosDeltApplied": {"dmtype": "ivoa:boolean", "value": True},
"errors": {

@@ -315,3 +314,3 @@ "dmrole": "mango:EpochPosition.errors",

"dmtype": "mango:ErrorTypes.PropertyError1D",
"sigma": {"value": 0.06909999996423721, "unit": "mas"},
"sigma": {"dmtype": "ivoa:real", "value": 0.06909999996423721, "unit": "mas"},
},

@@ -321,3 +320,3 @@ "radialVelocity": {

"dmtype": "mango:ErrorTypes.PropertyError1D",
"sigma": {"value": None, "unit": "km/s"},
"sigma": {"dmtype": "ivoa:real", "value": None, "unit": "km/s"},
},

@@ -327,4 +326,4 @@ "position": {

"dmtype": "mango:ErrorTypes.ErrorMatrix",
"sigma1": {"value": 0.0511, "unit": "mas"},
"sigma2": {"value": 0.0477, "unit": "mas"},
"sigma1": {"dmtype": "ivoa:real", "value": 0.0511, "unit": "mas"},
"sigma2": {"dmtype": "ivoa:real", "value": 0.0477, "unit": "mas"},
},

@@ -334,4 +333,4 @@ "properMotion": {

"dmtype": "mango:ErrorTypes.ErrorMatrix",
"sigma1": {"value": 0.06400000303983688, "unit": "mas/yr"},
"sigma2": {"value": 0.06700000166893005, "unit": "mas/yr"},
"sigma1": {"dmtype": "ivoa:real", "value": 0.06400000303983688, "unit": "mas/yr"},
"sigma2": {"dmtype": "ivoa:real", "value": 0.06700000166893005, "unit": "mas/yr"},
},

@@ -345,7 +344,7 @@ },

"dmtype": "mango:Correlation22",
"isCovariance": {"value": True},
"a2b1": {"value": -0.0085},
"a2b2": {"value": -0.2983},
"a1b1": {"value": -0.4109},
"a1b2": {"value": -0.0072},
"isCovariance": {"dmtype": "ivoa:boolean", "value": True},
"a2b1": {"dmtype": "ivoa:real", "value": -0.0085},
"a2b2": {"dmtype": "ivoa:real", "value": -0.2983},
"a1b1": {"dmtype": "ivoa:real", "value": -0.4109},
"a1b2": {"dmtype": "ivoa:real", "value": -0.0072},
},

@@ -355,5 +354,5 @@ "parallaxPm": {

"dmtype": "mango:Correlation12",
"isCovariance": {"value": True},
"a1b1": {"value": -0.2603},
"a1b2": {"value": -0.0251},
"isCovariance": {"dmtype": "ivoa:boolean", "value": True},
"a1b1": {"dmtype": "ivoa:real", "value": -0.2603},
"a1b2": {"dmtype": "ivoa:real", "value": -0.0251},
},

@@ -363,5 +362,5 @@ "positionParallax": {

"dmtype": "mango:Correlation21",
"isCovariance": {"value": True},
"a2b1": {"value": 0.0069},
"a1b1": {"value": 0.1337},
"isCovariance": {"dmtype": "ivoa:boolean", "value": True},
"a2b1": {"dmtype": "ivoa:real", "value": 0.0069},
"a1b1": {"dmtype": "ivoa:real", "value": 0.1337},
},

@@ -371,5 +370,5 @@ "positionPosition": {

"dmtype": "mango:Correlation22",
"isCovariance": {"value": True},
"a2b1": {"value": 0.1212},
"a1b2": {"value": 0.1212},
"isCovariance": {"dmtype": "ivoa:boolean", "value": True},
"a2b1": {"dmtype": "ivoa:real", "value": 0.1212},
"a1b2": {"dmtype": "ivoa:real", "value": 0.1212},
},

@@ -379,5 +378,5 @@ "properMotionPm": {

"dmtype": "mango:Correlation22",
"isCovariance": {"value": True},
"a2b1": {"value": 0.2688},
"a1b2": {"value": 0.2688},
"isCovariance": {"dmtype": "ivoa:boolean", "value": True},
"a2b1": {"dmtype": "ivoa:real", "value": 0.2688},
"a1b2": {"dmtype": "ivoa:real", "value": 0.2688},
},

@@ -392,3 +391,3 @@ },

"dmtype": "coords:SpaceFrame",
"spaceRefFrame": {"value": "ICRS"},
"spaceRefFrame": {"dmtype": "ivoa:string", "value": "ICRS"},
},

@@ -411,7 +410,8 @@ },

radius=0.05,
)
),
resolve_ref=True
)
mivot_instance = m_viewer.dm_instance
assert mivot_instance.dmtype == "mango:EpochPosition"
assert mivot_instance.coordSys.spaceRefFrame.value == "ICRS"
assert mivot_instance.spaceSys.frame.spaceRefFrame.value == "ICRS"
ra = []

@@ -421,3 +421,4 @@ dec = []

pmdec = []
while m_viewer.next():
while m_viewer.next_row_view():
ra.append(mivot_instance.longitude.value)

@@ -424,0 +425,0 @@ dec.append(mivot_instance.latitude.value)

@@ -23,3 +23,3 @@ '''

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from pyvo.mivot.utils.exceptions import MivotError

@@ -86,4 +86,4 @@

"""
m_viewer = MivotViewer(votable_path=path_to_withname)
m_viewer.next()
m_viewer = MivotViewer(votable_path=path_to_withname, resolve_ref=True)
m_viewer.next_row_view()
mivot_object = m_viewer.dm_instance

@@ -98,3 +98,3 @@

m_viewer.next()
m_viewer.next_row_view()

@@ -116,3 +116,3 @@ assert abs(mivot_object.longitude.value - 32.2340018) < delt_coo

m_viewer = MivotViewer(votable_path=path_to_withid)
m_viewer.next()
m_viewer.next_row_view()
mivot_instance = m_viewer.dm_instance

@@ -119,0 +119,0 @@ assert abs(mivot_instance.longitude.value - 52.2340018) < delt_coo

@@ -12,3 +12,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot import MivotViewer
from pyvo.mivot.viewer import MivotViewer
from pyvo.mivot.utils.exceptions import MivotError

@@ -20,3 +20,3 @@

m_viewer.next()
m_viewer.next_row_view()
xml_viewer = m_viewer.xml_viewer

@@ -23,0 +23,0 @@ with pytest.raises(MivotError,

@@ -16,2 +16,24 @@ """

@staticmethod
def add_array_element(dictionnary, key):
"""
Add a [] as key element if not exist
Parameters:
-----------
dictionnary: dict
dictionnary to be updated
key: string
key to be added
Returns:
--------
Bool
True if the key element has been added
"""
if key not in dictionnary:
dictionnary[key] = []
return True
return False
@staticmethod
def read_dict_from_file(filename, fatal=False):

@@ -31,9 +53,9 @@ """

from collections import OrderedDict
with open(filename, 'r') as file:
with open(filename) as file:
return json.load(file, object_pairs_hook=OrderedDict)
except Exception as exception:
if fatal:
raise MivotError("reading {}".format(filename))
raise MivotError(f"reading {filename}")
else:
logging.error("{} reading {}".format(exception, filename))
logging.error(f"{exception} reading {filename}")

@@ -40,0 +62,0 @@ @staticmethod

@@ -30,2 +30,2 @@ import json

else:
return super(MivotJsonEncoder, self).default(obj)
return super().default(obj)
'''
Some utilities making easier the transformation of Mivot elements into dictionary components.
These dictionaries are used to generate ``MivotInstance`` objects
Utilities handling various operations on Mivot instances
'''
import numpy
from pyvo.mivot.utils.exceptions import MappingError
from pyvo.mivot.glossary import IvoaType, Roles, ModelPrefix
class MivotUtils(object):
class MivotUtils:
"""
Some utilities making easier the transformation of Mivot elements into dictionary components.
These dictionaries are used to generate ``MivotInstance`` objects
"""
@staticmethod
def _valid_mapped_dmroles(mapped_roles, class_name):
"""
Check that the given mapped roles of the given class, are in the `pyvo.mivot.glossary.Roles`,
which reflects the model.
Parameters
----------
mapped_roles: dict
Dictionary of the matches between the dmroles (dict keys) and
the column identifiers (dict values). This mapping is a user input.
class_name: str
Name of the class to which the mapping applies
Returns
-------
dict
The dictionary of valid matches between the dmroles (dict keys) and
the column identifiers (dict values).
Raises
------
MappingError
If the class ``class_name`` is not supported or if
some mapped role does not exist for the glossary
"""
# Check that the class is in the glossary
if not hasattr(Roles, class_name):
raise MappingError(f"Unknown or unimplemented class {class_name}")
# get the list of supported roles
dmroles = getattr(Roles, class_name)
real_mapping = []
for mapped_role, column in mapped_roles:
# 'class' is a reserved word, not a role
if mapped_role == "class" or isinstance(column, dict) or isinstance(column, list):
continue
found = False
for leaf in dmroles:
dmrole = f"{ModelPrefix.mango}:{class_name}.{leaf}"
if dmrole.lower().endswith("." + mapped_role.lower()):
real_mapping.append((dmrole, column))
found = True
break
if not found:
raise MappingError(f"Class {ModelPrefix.mango}:{class_name} "
f"has no {mapped_role} attribute."
f"Supported roles are {dmroles}")
return real_mapping
@staticmethod
def xml_to_dict(element):
"""
Recursively create a nested dictionary from the XML tree structure, preserving the hierarchy.
Each object in the dictionary is represented by a new dictionary with dmrole: {}.
Recursively create a nested dictionary from the XML tree structure,
preserving the hierarchy.
The processing of elements depends on the tag:
- For INSTANCE, a new dictionary is created.
- For COLLECTION, a list is created.
- For ATTRIBUTE, a leaf is created in the tree structure with dmtype, dmrole, value, unit, and ref.
- For ATTRIBUTE, a leaf structure is created in the tree structure with dmtype,
dmrole, value, unit, and ref keys.
Parameters
----------
element (~`xml.etree.ElementTree.Element`) : The XML element to convert to a dictionary.
element : `xml.etree.ElementTree.Element`
The XML element to convert to a dictionary
Returns
-------
dict: The nested dictionary representing the XML tree structure.
dict
The nested dictionary representing the XML tree structure.
"""

@@ -31,50 +94,57 @@ dict_result = {}

if child.tag == "ATTRIBUTE":
dict_result[dmrole] = MivotUtils.attribute_to_dict(child)
dict_result[dmrole] = MivotUtils._attribute_to_dict(child)
elif child.tag == "INSTANCE": # INSTANCE is recursively well managed by the function _to_dict
dict_result[dmrole] = MivotUtils.xml_to_dict(child)
elif child.tag == "COLLECTION":
dict_result[dmrole] = MivotUtils.collection_to_dict(child)
dict_result[dmrole] = MivotUtils._collection_to_dict(child)
return dict_result
@staticmethod
def attribute_to_dict(child):
def _attribute_to_dict(child):
"""
Convert an ATTRIBUTE element to a dictionary.
ATTRIBUTE is always a leaf, so it is not recursive.
Convert an ATTRIBUTE (XML) element to a dictionary.
ATTRIBUTE being always a leaf, the conversion is not recursive.
Parameters
----------
child (~`xml.etree.ElementTree.Element`): ATTRIBUTE XML element to convert.
child : `xml.etree.ElementTree.Element`
ATTRIBUTE XML element to convert.
Returns
-------
dict: A dictionary representing the ATTRIBUTE element with keys:
'dmtype', 'dmrole', 'value', 'unit', and 'ref'.
dict:
A dictionary representing the ATTRIBUTE element with keys:
'dmtype', 'dmrole', 'value', 'unit', and 'ref'.
"""
attribute = {}
if child.get('dmtype') is not None:
attribute['dmtype'] = child.get("dmtype")
if child.get("dmtype") is not None:
attribute["dmtype"] = child.get("dmtype")
if child.get("value") is not None:
attribute['value'] = MivotUtils.cast_type_value(child.get("value"), child.get("dmtype"))
attribute["value"] = MivotUtils.cast_type_value(child.get("value"), child.get("dmtype"))
else:
attribute['value'] = None
attribute["value"] = None
if child.get("unit") is not None:
attribute['unit'] = child.get("unit")
attribute["unit"] = child.get("unit")
else:
attribute['unit'] = None
attribute["unit"] = None
if child.get("ref") is not None:
attribute['ref'] = child.get("ref")
attribute["ref"] = child.get("ref")
else:
attribute['ref'] = None
attribute["ref"] = None
return attribute
@staticmethod
def collection_to_dict(child):
def _collection_to_dict(child):
"""
Convert a COLLECTION element to a list of dictionaries.
COLLECTION is always represented as a list, and each element of the COLLECTION is added to the list.
Convert a COLLECTION element (child) to a list of dictionaries.
Parameters
----------
child (`~`xml.etree.ElementTree.Element``): COLLECTION XML element to convert.
child : `xml.etree.ElementTree.Element`
COLLECTION XML element to convert
Returns
-------
list: list of dictionaries representing the elements of the COLLECTION.
list({})
list of dictionaries representing the COLLECTION items
"""

@@ -89,17 +159,22 @@ collection_items = []

"""
Cast the value of an ATTRIBUTE based on its dmtype.
As the type of ATTRIBUTE values returned in the dictionary is string by default,
this function is used to cast them based on their dmtype.
Cast value to the Python type matching dmtype.
Parameters
----------
value (str): value of the ATTRIBUTE.
dmtype (str): dmtype of the ATTRIBUTE.
value : str
value to cast
dmtype : str
model dmtype
Returns
-------
Union[bool, float, str, None]
The cast value based on the dmtype.
The casted value or None
"""
if type(value) is numpy.float32 or type(value) is numpy.float64:
lower_dmtype = dmtype.lower()
# empty strings cannot be casted
if "string" not in lower_dmtype and value == "":
return None
if numpy.issubdtype(type(value), numpy.floating):
return float(value)
lower_dmtype = dmtype.lower()
if isinstance(value, str):

@@ -123,1 +198,125 @@ lower_value = value.lower()

return value
@staticmethod
def format_dmid(dmid):
"""
Replace characters that could confuse XPath queries with '_'.
This is not required by the MIVOT schema but this makes this API more robust
Returns
-------
str
formatted dmid
"""
if dmid is not None:
return dmid.replace("/", "_").replace(".", "_").replace("-", "_")
return ""
@staticmethod
def get_field_attributes(table, column_id):
"""
Parameters
----------
table : astropy.table
Table (from parsed VOTable) of the mapped data
column_id : str
Identifier of the table column from which we want to get the unit
Returns
-------
unit, ref, literal
"""
ref, literal = MivotUtils.get_ref_or_literal(column_id)
if literal:
return None, None, literal
else:
try:
field = table.get_field_by_id_or_name(ref)
return str(field.unit), column_id, None
except KeyError as keyerror:
raise MappingError(f"Cannot find any field identified by {column_id}") from keyerror
@staticmethod
def get_ref_or_literal(value_or_ref):
"""
Check if value_or_ref must be interpreted as a column reference or a literal.
Returns
-------
(ref, literal)
"""
if not value_or_ref:
raise MappingError("An attribute cannot be set with a None value")
elif isinstance(value_or_ref, str):
return ((None, value_or_ref.replace("*", "")) if value_or_ref.startswith("*")
else (value_or_ref, None))
else:
return (None, value_or_ref)
@staticmethod
def as_literal(identifier):
"""
Make sure the identifier will be interpreted as a literal (* prefix).
Literal are either non string values or strings starting with a *
Parameters
----------
identifier: str
column identifier or literal value
Returns
-------
str
identifier prefixes with a *
"""
if isinstance(identifier, str) and not identifier.startswith("*"):
return "*" + identifier
return identifier
@staticmethod
def populate_instance(property_instance, class_name,
mapping, table, dmtype, as_literals=False, package=None):
"""
This function inserts in the property_instance all expected attributes.
- The structure of the class is supposed to be flat (only ATTRIBUTEs).
- All attributes are meant to have the same dmtype.
- The mapping is checked against the `pyvo.mivot.glossary.Roles`.
Parameters
----------
property_instance : `pyvo.mivot.writer.instance.MivotInstance`
Mivot instance to populate with attributes
class_name : str
Name of the property_instance class (dmtype).
Used to get all the attribute roles (given by the model) of the class
mapping : dict
Dictionary associating model roles with their values.
table : astropy.table
Table (from parsed VOTable) of the mapped data
dmtype : string
common dmtype of object attributes
as_literal : boolean, optional (default isTrue)
If True, all attribute are set with literal values (@value="...")
package : str, optional (default as None)
Package name possibly prefixing dmroles
"""
mapped_roles = MivotUtils._valid_mapped_dmroles(mapping.items(), class_name)
pkg = f"{package}." if package else ""
for dmrole, column in mapped_roles:
# minimal reserved characters escaping
if isinstance(column, str):
column = column.replace("&", "&amp;")
# force column references to be processed as literals if requested
if as_literals:
column = MivotUtils.as_literal(column)
unit, _, _ = MivotUtils.get_field_attributes(table, column)
if isinstance(column, bool):
r_dmtype = IvoaType.bool
else:
r_dmtype = dmtype
r_dmrole = dmrole.replace(":", f":{pkg}")
property_instance.add_attribute(dmtype=r_dmtype,
dmrole=r_dmrole,
value=column,
unit=unit)
"""
Utility class to process XML.
"""
import re
from pyvo.mivot.utils.xpath_utils import XPath
import xml.etree.ElementTree as ET
from xml.dom import minidom
from pyvo.mivot.utils.vocabulary import Constant

@@ -16,18 +18,34 @@ from pyvo.mivot.utils.vocabulary import Att

@staticmethod
def pretty_print(xmltree):
def pretty_print(xmltree, *, lshift=""):
"""
Pretty print an XML tree.
Pretty print an XML tree
Parameters
----------
xmltree (~`xml.etree.ElementTree.Element`): XML tree to pretty print.
xmltree: (~`xml.etree.ElementTree.Element`)
XML tree to pretty print
lshift : str, optional, default ""
Sequence to be inserted at the beginning of each line
Usually a space sequence
"""
print(XmlUtils.pretty_string(xmltree))
print(XmlUtils.pretty_string(xmltree, lshift=lshift))
@staticmethod
def pretty_string(xmltree):
def pretty_string(xmltree, *, lshift="", clean_namespace=True):
"""
Return a pretty string representation of an XML tree.
Return a pretty string representation of an XML tree (as Etree or string)
Parameters
----------
xmltree (~`xml.etree.ElementTree.Element`): XML tree to convert to a pretty string.
xmltree (~`xml.etree.ElementTree.Element`) or string:
XML tree to convert to a pretty string
lshift : str, optional, default ""
Sequence to be inserted at the beginning of each line
Usually a space sequence
clean_namespace : boolean, optional, default True
Default namespace (ns0) removed from element names if True
Returns

@@ -37,38 +55,36 @@ -------

"""
if hasattr(xmltree, 'getroot'):
XmlUtils.indent(xmltree.getroot())
new_xml = ET.tostring(xmltree.getroot(), encoding='unicode')
if isinstance(xmltree, str):
root = xmltree
else:
XmlUtils.indent(xmltree)
new_xml = ET.tostring(xmltree, encoding='unicode')
return new_xml.replace("ns0:", "")
if hasattr(xmltree, 'getroot'):
root = ET.tostring(xmltree.getroot(), encoding='unicode')
else:
root = ET.tostring(xmltree, encoding='unicode')
root = root.replace("<?xml version=\"1.0\" ?>\n", "")
reparsed = minidom.parseString(root)
pretty_string = re.sub(r" +\n", "", reparsed.toprettyxml(indent=" "))
pretty_string = pretty_string.replace("<?xml version=\"1.0\" ?>\n", "") \
.replace("\n\n", "\n") \
.replace("<", f"{lshift}<")
if clean_namespace:
return pretty_string.replace("ns0:", "")
else:
return pretty_string
@staticmethod
def indent(elem, level=0):
def strip_xml(xml_string):
"""
Indent an XML tree.
Parameters
----------
elem (~`xml.etree.ElementTree.Element`): XML tree to indent.
level (int): level of indentation.
Returns
-------
~`xml.etree.ElementTree.Element`
The indented XML tree.
Strip unnecessary whitespace and newline characters from an XML string.
Used by unit tests to compare xml strings
Parameters:
- xml_string (str): The XML string to strip.
Returns:
- str: The stripped XML string.
"""
i = "\n" + level * " "
j = "\n" + (level - 1) * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for subelem in elem:
XmlUtils.indent(subelem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = j
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = j
return elem
return (
xml_string.replace("\n", "").replace(" ", "").replace("'", "").replace('"', "")
)

@@ -75,0 +91,0 @@ @staticmethod

@@ -75,12 +75,3 @@ """

"""
cnt = 1
result = []
run = True
while run:
if etree.find(path + str(cnt)) is not None:
result.append(etree.find(path + str(cnt)))
cnt += 1
else:
run = False
return result
return {elem for elem in etree.iter() if elem.tag.startswith("REFERENCE_")}

@@ -87,0 +78,0 @@ @staticmethod

@@ -121,3 +121,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

value.update(row=row)
if 'value' in vars(value):
if 'ref' in vars(value):
value.update(row=row, ref=getattr(value, 'ref'))

@@ -169,3 +169,3 @@ else:

def _get_class_dict(self, obj, classkey=None, slim=False):
def _get_class_dict(self, obj, classkey=None, slim=False, with_dmtypes=True):
"""

@@ -183,2 +183,4 @@ Recursively displays a serializable dictionary.

@dmtype and @ref attributes are ignored
with_dmtypes (boolean, optional) : if true dmtypes are added to the
primitive types (model leaves)
Returns

@@ -200,9 +202,9 @@ -------

elif hasattr(obj, "__dict__"):
data = dict([(key, obj._get_class_dict(value, classkey, slim=slim))
data = {key: obj._get_class_dict(value, classkey, slim=slim)
for key, value in obj.__dict__.items()
if not callable(value) and not key.startswith('_')])
if not callable(value) and not key.startswith('_')}
# remove the house keeping parameters
if slim is True:
# data is atomic value (e.g. float): the type be hidden
if "ref" in data or "value" in data:
if with_dmtypes is False and ("ref" in data or "value" in data):
data.pop("dmtype", None)

@@ -209,0 +211,0 @@ # remove unit when not set

@@ -18,3 +18,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

mivot_object = mivot_viewer.dm_instance
while mivot_viewer.next():
while mivot_viewer.next_row_view():
print(f"latitude={mivot_object.latitude.value}")

@@ -60,3 +60,3 @@ print(f"longitude={mivot_object.longitude.value}")

"""
def __init__(self, votable_path, tableref=None):
def __init__(self, votable_path, tableref=None, resolve_ref=False):
"""

@@ -73,2 +73,9 @@ Constructor of the MivotViewer class.

the first table is taken by default.
Parameters
----------
resolve_ref : bool, optional
If True, replace the REFERENCE elements with a copy of the objects they refer to.
e.g. copy the space coordinates system, usually located in the GLOBALS
block, in the position objects
Default is False.
"""

@@ -99,2 +106,3 @@ if not check_astropy_version():

self._dm_instance = None
self._resolve_ref = resolve_ref
try:

@@ -195,3 +203,3 @@ self._set_resource()

def next(self):
def next_row_view(self):
"""

@@ -379,14 +387,9 @@ jump to the next table row and update the MivotInstance instance

def _get_model_view(self, resolve_ref=True):
def _get_model_view(self):
"""
Return an XML model view of the last read row.
This function resolves references by default.
Parameters
----------
resolve_ref : bool, optional
If True, resolves the references. Default is True.
"""
templates_copy = deepcopy(self._templates)
if resolve_ref is True:
if self._resolve_ref is True:
while StaticReferenceResolver.resolve(self._annotation_seeker, self._connected_tableref,

@@ -393,0 +396,0 @@ templates_copy) > 0:

@@ -15,3 +15,4 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

Freetext, Author, Servicetype, Waveband, Datamodel, Ivoid,
UCD, Spatial, Spectral, Temporal, RegTAPFeatureMissing)
UCD, UAT, Spatial, Spectral, Temporal,
RegTAPFeatureMissing)

@@ -21,4 +22,4 @@ __all__ = ["search", "get_RegTAP_query", "Constraint", "SubqueriedConstraint",

"Servicetype", "Waveband", "Datamodel", "Ivoid", "UCD",
"Spatial", "Spectral", "Temporal",
"UAT", "Spatial", "Spectral", "Temporal",
"choose_RegTAP_service", "RegTAPFeatureMissing",
"RegistryResults", "RegistryResource",]

@@ -321,11 +321,11 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

@functools.lru_cache(maxsize=None)
@functools.cache
def _get_ivo_index(self):
return dict((r.ivoid, index)
for index, r in enumerate(self))
return {r.ivoid: index
for index, r in enumerate(self)}
@functools.lru_cache(maxsize=None)
@functools.cache
def _get_short_name_index(self):
return dict((r.short_name, index)
for index, r in enumerate(self))
return {r.short_name: index
for index, r in enumerate(self)}

@@ -732,6 +732,6 @@ def __getitem__(self, item):

"""
return set(shorten_stdid(intf.standard_id) or "web"
return {shorten_stdid(intf.standard_id) or "web"
for intf in self.interfaces
if (intf.standard_id or intf.type == "vr:webbrowser")
and not intf.is_vosi)
and not intf.is_vosi}

@@ -738,0 +738,0 @@ def get_interface(self, *,

@@ -35,19 +35,19 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

# from regtap.
SERVICE_TYPE_MAP = dict((k, "ivo://ivoa.net/std/" + v)
SERVICE_TYPE_MAP = {k: "ivo://ivoa.net/std/" + v
for k, v in [
("image", "sia"),
("sia", "sia"),
("sia1", "sia"),
# SIA2 is irregular
# funky scheme used by SIA2 without breaking everything else
("spectrum", "ssa"),
("ssap", "ssa"),
("ssa", "ssa"),
("scs", "conesearch"),
("conesearch", "conesearch"),
("line", "slap"),
("slap", "slap"),
("table", "tap"),
("tap", "tap"),
])
("image", "sia"),
("sia", "sia"),
("sia1", "sia"),
# SIA2 is irregular
# funky scheme used by SIA2 without breaking everything else
("spectrum", "ssa"),
("ssap", "ssa"),
("ssa", "ssa"),
("scs", "conesearch"),
("conesearch", "conesearch"),
("line", "slap"),
("slap", "slap"),
("table", "tap"),
("tap", "tap"),
]}

@@ -110,4 +110,7 @@

elif isinstance(value, datetime.datetime):
return "'{}'".format(value.isoformat())
return f"'{value.isoformat()}'"
elif isinstance(value, set):
return '('+", ".join(make_sql_literal(s) for s in sorted(value))+')'
else:

@@ -294,4 +297,4 @@ raise ValueError("Cannot format {} as a SQL literal"

for index, word in enumerate(self.words):
parname = "fulltext{}".format(index)
parpatname = "fulltextpar{}".format(index)
parname = f"fulltext{index}"
parpatname = f"fulltextpar{index}"
self._fillers[parname] = word

@@ -316,4 +319,4 @@ self._fillers[parpatname] = '%' + word + '%'

for index, word in enumerate(self.words):
parname = "fulltext{}".format(index)
parpatname = "fulltextpar{}".format(index)
parname = f"fulltext{index}"
parpatname = f"fulltextpar{index}"
self._fillers[parname] = word

@@ -463,4 +466,4 @@ self._fillers[parpatname] = '%' + word + '%'

expanded = self.clone()
expanded.stdids |= set(
std + '#aux' for std in expanded.stdids)
expanded.stdids |= {
std + '#aux' for std in expanded.stdids}
if "standard_id like 'ivo://ivoa.net/std/sia#query-2.%'" in expanded.extra_fragments:

@@ -641,6 +644,93 @@ expanded.extra_fragments.append(

f"ucd LIKE {{ucd{i}}}" for i in range(len(patterns)))
self._fillers = dict((f"ucd{index}", pattern)
for index, pattern in enumerate(patterns))
self._fillers = {f"ucd{index}": pattern
for index, pattern in enumerate(patterns)}
class UAT(SubqueriedConstraint):
"""
A constraint selecting resources having UAT keywords as subjects.
The UAT (Unified Astronomy Thesaurus) is a hierarchical system
of concepts in astronomy. In the VO, its concept identifiers
are dashed strings, something like ``x-ray-transient-sources``.
The full list of identifiers is available from
http://www.ivoa.net/rdf/uat.
Note that not all data providers properly use UAT keywords in their
subjects even in 2025 (they should, though), and their keyword
assignments may not always be optimal. Consider doing free
text searches if UAT-based results are disappointing, and then
telling the respective data providers about missing keywords.
"""
_keyword = "uat"
_subquery_table = "rr.res_subject"
_condition = "res_subject in {query_terms}"
_uat = None
@classmethod
def _expand(cls, term, level, direction):
"""
Recursively expand term in the uat.
This returns a set of concepts that are ``level`` levels wider
or narrower (depending on the value of ``direction``) than term.
This function assumes the _uat class attribute has been filled
before; that is the case once a constraint has been constructed.
Parameters
----------
term: str
the start term
level: int
expand this many levels
direction: str
either ``wider`` to expand towards more general concepts
or ``narrower`` to expand toward more specialised concepts.
"""
result = {term}
new_concepts = cls._uat[term][direction]
if level:
for concept in new_concepts:
result |= cls._expand(concept, level-1, direction)
return result
def __init__(self, uat_keyword, *, expand_up=0, expand_down=0):
"""
Parameters
----------
uat_keyword: str
An identifier from http://www.ivoa.net/rdf/uat, i.e., a
string like type-ib-supernovae. Note that these are
always all-lowercase.
expand_up: int
In addition to the concept itself, also include expand_up
levels of parent concepts (this is probably rarely makes
sense beyond 1).
expand_down: int
In addition to the concept itself, also include expand_down
levels of more specialised concepts (this is usually a good
idea; having more than 10 here for now is equivalent to
infinity).
"""
if self.__class__._uat is None:
self.__class__._uat = vocabularies.get_vocabulary("uat")["terms"]
if uat_keyword not in self._uat:
raise dalq.DALQueryError(
f"{uat_keyword} does not identify an IVOA uat"
" concept (see http://www.ivoa.net/rdf/uat).")
query_terms = {uat_keyword}
if expand_up:
query_terms |= self._expand(uat_keyword, expand_up, "wider")
if expand_down:
query_terms |= self._expand(uat_keyword, expand_down, "narrower")
self._fillers = {"query_terms": query_terms}
class Spatial(SubqueriedConstraint):

@@ -737,3 +827,3 @@ """

def tomoc(s):
return _AsIs("MOC({}, {})".format(order, s))
return _AsIs(f"MOC({order}, {s})")

@@ -740,0 +830,0 @@ if isinstance(geom_spec, str):

@@ -317,2 +317,3 @@ #!/usr/bin/env python

"""
def __init__(self, **kwargs):

@@ -330,2 +331,3 @@ for k, v in kwargs.items():

"""
def __init__(self, valdict):

@@ -732,3 +734,3 @@ self.fieldnames = list(valdict.keys())

'doi:10.26093/cds/vizier.1337',
'bibcode:2016yCat.1337....0G'}
}

@@ -735,0 +737,0 @@

@@ -287,2 +287,32 @@ #!/usr/bin/env python

@pytest.mark.remote_data
class TestUATConstraint:
def test_basic(self):
cons = rtcons.UAT("solar-flares")
assert (cons.get_search_condition(FAKE_GAVO)
== "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in ('solar-flares'))")
def test_nonterm(self):
with pytest.raises(dalq.DALQueryError, match="solarium does not identify"):
rtcons.UAT("solarium")
def test_wider(self):
cons = rtcons.UAT("solar-flares", expand_up=2)
assert (cons.get_search_condition(FAKE_GAVO)
== "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in"
" ('solar-activity', 'solar-flares', 'solar-physics', 'solar-storm'))")
def test_narrower(self):
cons = rtcons.UAT("solar-activity", expand_down=1)
assert (cons.get_search_condition(FAKE_GAVO)
== "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in"
" ('solar-active-regions', 'solar-activity', 'solar-filaments', 'solar-flares',"
" 'solar-magnetic-bright-points', 'solar-prominences', 'solar-storm'))")
cons = rtcons.UAT("solar-activity", expand_down=2)
assert (cons.get_search_condition(FAKE_GAVO).startswith(
"ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in"
" ('ephemeral-active-regions', 'quiescent-solar-prominence',"
" 'solar-active-region-filaments'"))
class TestSpatialConstraint:

@@ -526,3 +556,3 @@ def test_point(self):

" author, datamodel, ivoid, keywords, servicetype,"
" spatial, spectral, temporal, ucd, waveband.")
" spatial, spectral, temporal, uat, ucd, waveband.")

@@ -619,5 +649,6 @@ def test_with_legacy_keyword(self):

time = rtcons.Temporal((50000, 60000))
uat = rtcons.UAT('galaxies', expand_down=3)
result = registry.search(
text, author, servicetype, waveband, datamodel,
ivoid, ucd, moc, spectral, time
ivoid, ucd, moc, spectral, time, uat
)

@@ -624,0 +655,0 @@ assert result.fieldnames == (

import inspect
import warnings
from functools import wraps
from typing import Dict, Iterable
from collections.abc import Iterable
from .protofeature import Feature

@@ -11,3 +11,3 @@

features: Dict[str, "Feature"] = {
features: dict[str, "Feature"] = {
'cadc-tb-upload': Feature('cadc-tb-upload',

@@ -14,0 +14,0 @@ 'https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next',

@@ -29,3 +29,3 @@ """

@functools.lru_cache()
@functools.lru_cache
def get_vocabulary(voc_name, force_update=False):

@@ -65,3 +65,3 @@ """returns an IVOA vocabulary in its "desise" form.

with open(src_name, "r", encoding="utf-8") as f:
with open(src_name, encoding="utf-8") as f:
return json.load(f)

@@ -68,0 +68,0 @@

@@ -15,3 +15,3 @@ # Licensed under a 3-clause BSD style license - see LICENSE.rst

return '{}:{}:{}: {}: {}'.format(filename, pos[0], pos[1], name, message)
return f'{filename}:{pos[0]}:{pos[1]}: {name}: {message}'

@@ -18,0 +18,0 @@

@@ -8,2 +8,2 @@ # Note that we need to fall back to the hard-coded version if either

except Exception:
version = '1.6.2'
version = '1.7'

@@ -34,3 +34,3 @@ PyVO

Source code can be found `on GitHub <http://github.com/astropy/pyvo>`_
Source code can be found `on GitHub <https://github.com/astropy/pyvo>`_

@@ -40,3 +40,3 @@ Installation and Requirements

Releases of PyVO are available from `PyPI <https://pypi.python.org/pypi/pyvo>`_
Releases of PyVO are available from `PyPI <https://pypi.org/project/pyvo/>`_
thus, it and its prerequisites can be most easily installed using ``pip``:

@@ -56,3 +56,3 @@

* `astropy <https://astropy.org>`__ (>=4.1)
* `requests <http://docs.python-requests.org/en/latest/>`_
* `requests <https://docs.python-requests.org/en/latest/>`_

@@ -93,3 +93,3 @@ The following packages are optional dependencies and are required for the

Many instructive examples can be found in the `PyVO Documentation <http://pyvo.readthedocs.org>`_.
Many instructive examples can be found in the `PyVO Documentation <https://pyvo.readthedocs.io/en/latest/>`_.
Additional examples can be found in the examples directory.

@@ -96,0 +96,0 @@

@@ -17,5 +17,2 @@ [tool:pytest]

ignore:numpy.core:DeprecationWarning
ignore::astropy.utils.iers.iers.IERSStaleWarning
ignore:Importing ErfaWarning:astropy.utils.exceptions.AstropyDeprecationWarning
ignore::astropy.utils.exceptions.ErfaWarning
ignore:pyvo.discover:pyvo.utils.prototype.PrototypeWarning

@@ -59,5 +56,5 @@

install_requires =
astropy>=4.1
astropy>=4.2
requests
python_requires = >=3.8
python_requires = >=3.9

@@ -64,0 +61,0 @@ [options.extras_require]

@@ -6,3 +6,3 @@ [tox]

envlist =
py{38,39,310,311,312,313}-test{,-alldeps,-oldestdeps,-devdeps}{,-online}{,-cov}
py{39,310,311,312,313}-test{,-alldeps,-oldestdeps,-devdeps}{,-online}{,-cov}
linkcheck

@@ -17,3 +17,3 @@ codestyle

extras =
test: test
test
alldeps: all

@@ -31,4 +31,2 @@

devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/scientific-python-nightly-wheels/simple https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/astropy/simple
# astropy doesn't yet have a 3.13 compatible release
py313: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/astropy/simple

@@ -42,9 +40,7 @@ deps =

# astropy doesn't yet have a 3.13 compatible release
py313: astropy>=0.0dev0
oldestdeps: astropy==4.2
oldestdeps: astropy==4.1
# We set a suitably old numpy along with an old astropy, no need to pick up
# deprecations and errors due to their unmatching versions
oldestdeps: numpy==1.19
oldestdeps: numpy==1.20

@@ -51,0 +47,0 @@ online: pytest-rerunfailures

Sorry, the diff of this file is too big to display