22"""
33Utility transforming MIVOT annotation into SkyCoord instances
44"""
5-
5+ import numbers
66from astropy .coordinates import SkyCoord
77from astropy import units as u
88from astropy .coordinates import ICRS , Galactic , FK4 , FK5
9- from pyvo .mivot .utils .exceptions import NoMatchingDMTypeError
9+ from astropy .time .core import Time
10+ from pyvo .mivot .utils .exceptions import NoMatchingDMTypeError , MappingError
1011
1112
1213class MangoRoles :
@@ -62,7 +63,9 @@ def __init__(self, mivot_instance_dict):
6263 def build_sky_coord (self ):
6364 """
6465 Build a SkyCoord instance from the MivotInstance dictionary.
65- The operation requires the dictionary to have ``mango:EpochPosition`` as dmtype
66+ The operation requires the dictionary to have ``mango:EpochPosition`` as dmtype.
67+ This instance can be either the root of the dictionary or it can be one
68+ of the Mango properties if the root object is a mango:MangoObject instance
6669 This is a public method which could be extended to support other dmtypes.
6770
6871 returns
@@ -75,15 +78,28 @@ def build_sky_coord(self):
7578 NoMatchingDMTypeError
7679 if the SkyCoord instance cannot be built.
7780 """
78- if self ._mivot_instance_dict and self ._mivot_instance_dict ["dmtype" ] == "mango:EpochPosition" :
81+
82+ if self ._mivot_instance_dict and self ._mivot_instance_dict ["dmtype" ] == "mango:MangoObject" :
83+ property_dock = self ._mivot_instance_dict ["propertyDock" ]
84+ for mango_property in property_dock :
85+ if mango_property ["dmtype" ] == "mango:EpochPosition" :
86+ self ._mivot_instance_dict = mango_property
87+ return self ._build_sky_coord_from_mango ()
88+ raise NoMatchingDMTypeError (
89+ "No INSTANCE with dmtype='mango:EpochPosition' has been found:"
90+ " in the property dock of the MangoObject, "
91+ "cannot build a SkyCoord from annotations" )
92+
93+ elif self ._mivot_instance_dict and self ._mivot_instance_dict ["dmtype" ] == "mango:EpochPosition" :
7994 return self ._build_sky_coord_from_mango ()
8095 raise NoMatchingDMTypeError (
8196 "No INSTANCE with dmtype='mango:EpochPosition' has been found:"
8297 " cannot build a SkyCoord from annotations" )
8398
84- def _set_year_time_format (self , hk_field , besselian = False ):
99+ def _get_time_instance (self , hk_field , besselian = False ):
85100 """
86101 Format a date expressed in year as [scale]year
102+ - Exception possibly risen by Astropy are not caught
87103
88104 parameters
89105 ----------
@@ -94,33 +110,96 @@ def _set_year_time_format(self, hk_field, besselian=False):
94110
95111 returns
96112 -------
97- string or None
98- attribute value formatted as [scale]year
113+ Time instance or None
114+
115+ raise
116+ -----
117+ MappingError: if the Time instance cannot be built for some reason
99118 """
100- scale = "J" if not besselian else "B"
101119 # Process complex type "mango:DateTime
102- # only "year" representation are supported yet
103120 if hk_field ['dmtype' ] == "mango:DateTime" :
104121 representation = hk_field ['representation' ]['value' ]
105122 timestamp = hk_field ['dateTime' ]['value' ]
106- if representation == "year" :
107- return f"{ scale } { timestamp } "
123+ # Process simple attribute
124+ else :
125+ representation = hk_field .get ("unit" )
126+ timestamp = hk_field .get ("value" )
127+
128+ if not representation or not timestamp :
129+ raise MappingError (f"Cannot interpret field { hk_field } "
130+ f"as a { ('besselian' if besselian else 'julian' )} timestamp" )
131+
132+ time_instance = self . _build_time_instance (timestamp , representation , besselian )
133+ if not time_instance :
134+ raise MappingError (f"Cannot build a Time instance from { hk_field } " )
135+
136+ return time_instance
137+
138+ def _build_time_instance (self , timestamp , representation , besselian = False ):
139+ """
140+ Build a Time instance matching the input parameters.
141+ - Returns None if the parameters do not allow any Time setup
142+ - Exception possibly risen by Astropy are not caught at this level
143+
144+ parameters
145+ ----------
146+ timestamp: string or number
147+ The timestamp must comply with the given representation
148+ representation: string
149+ year, iso, ... (See MANGO primitive types derived from ivoa:timeStamp)
150+ besselian: boolean (optional)
151+ Flag telling to use the besselain calendar. We assume it to only be
152+ relevant for FK5 frame
153+ returns
154+ -------
155+ Time instance or None
156+ """
157+ if representation in ["year" , "yr" ]:
158+ # it the timestamp is numeric, we infer its format from the besselian flag
159+ if isinstance (timestamp , numbers .Number ):
160+ return Time (f"{ ('B' if besselian else 'J' )} { timestamp } " ,
161+ format = ("byear_str" if besselian else "jyear_str" ))
162+ if besselian :
163+ if timestamp .startswith ("B" ):
164+ return Time (f"{ timestamp } " , format = "byear_str" )
165+ elif timestamp .startswith ("J" ):
166+ # a besselain year cannot be given as "Jxxxx"
167+ return None
168+ elif timestamp .isnumeric ():
169+ # we force the string representation not to break the test assertions
170+ return Time (f"B{ timestamp } " , format = "byear_str" )
171+ else :
172+ if timestamp .startswith ("J" ):
173+ return Time (f"{ timestamp } " , format = "jyear_str" )
174+ elif timestamp .startswith ("B" ):
175+ # a julian year cannot be given as "Bxxxx"
176+ return None
177+ elif timestamp .isnumeric ():
178+ # we force the string representation not to break the test assertions
179+ return Time (f"J{ timestamp } " , format = "jyear_str" )
180+ # no case matches
108181 return None
109- return (f"{ scale } { hk_field ['value' ]} " if hk_field ["unit" ] in ("yr" , "year" )
110- else hk_field ["value" ])
182+ # in the following cases, the calendar (B or J) is givent by the besselian flag
183+ # We force to use the string representation to avoid breaking unit tests.
184+ elif representation == "mjd" :
185+ time = Time (f"{ timestamp } " , format = "mjd" )
186+ return (Time (time .byear_str ) if besselian else time )
187+ elif representation == "jd" :
188+ time = Time (f"{ timestamp } " , format = "jd" )
189+ return (Time (time .byear_str ) if besselian else time )
190+ elif representation == "iso" :
191+ time = Time (f"{ timestamp } " , format = "iso" )
192+ return (Time (time .byear_str ) if besselian else time )
193+
194+ return None
111195
112- def _get_space_frame (self , obstime = None ):
196+ def _get_space_frame (self ):
113197 """
114198 Build an astropy space frame instance from the MIVOT annotations.
115199
116200 - Equinox are supported for FK4/5
117201 - Reference location is not supported
118202
119- parameters
120- ----------
121- obstime: str
122- Observation time is given to the space frame builder (this method) because
123- it must be set by the coordinate system constructor in case of FK4 frame.
124203 returns
125204 -------
126205 FK2, FK5, ICRS or Galactic
@@ -133,14 +212,15 @@ def _get_space_frame(self, obstime=None):
133212 if frame == 'fk4' :
134213 self ._map_coord_names = skycoord_param_default
135214 if "equinox" in coo_sys :
136- equinox = self ._set_year_time_format (coo_sys ["equinox" ], True )
137- return FK4 (equinox = equinox , obstime = obstime )
215+ equinox = self ._get_time_instance (coo_sys ["equinox" ], True )
216+ # by FK4 takes obstime=equinox by default
217+ return FK4 (equinox = equinox )
138218 return FK4 ()
139219
140220 if frame == 'fk5' :
141221 self ._map_coord_names = skycoord_param_default
142222 if "equinox" in coo_sys :
143- equinox = self ._set_year_time_format (coo_sys ["equinox" ])
223+ equinox = self ._get_time_instance (coo_sys ["equinox" ])
144224 return FK5 (equinox = equinox )
145225 return FK5 ()
146226
@@ -153,9 +233,7 @@ def _get_space_frame(self, obstime=None):
153233
154234 def _build_sky_coord_from_mango (self ):
155235 """
156- Build silently a SkyCoord instance from the ``mango:EpochPosition instance``.
157- No error is trapped, unconsistencies in the ``mango:EpochPosition`` instance will
158- raise Astropy errors.
236+ Build a SkyCoord instance from the ``mango:EpochPosition instance``.
159237
160238 - The epoch (obstime) is meant to be given in year.
161239 - ICRS frame is taken by default
@@ -170,26 +248,31 @@ def _build_sky_coord_from_mango(self):
170248 kwargs = {}
171249 kwargs ["frame" ] = self ._get_space_frame ()
172250
173- for key , value in self ._map_coord_names .items ():
174- # ignore not set parameters
175- if key not in self ._mivot_instance_dict :
251+ for mango_role , skycoord_field in self ._map_coord_names .items ():
252+ # ignore not mapped parameters
253+ if mango_role not in self ._mivot_instance_dict :
176254 continue
177- hk_field = self ._mivot_instance_dict [key ]
178- # format the observation time (J-year by default)
179- if value == "obstime" :
180- # obstime must be set into the KK4 frame but not as an input parameter
181- fobstime = self ._set_year_time_format (hk_field )
182- if isinstance (kwargs ["frame" ], FK4 ):
183- kwargs ["frame" ] = self ._get_space_frame (obstime = fobstime )
255+ hk_field = self ._mivot_instance_dict [mango_role ]
256+ if mango_role == "obsDate" :
257+ besselian = isinstance (kwargs ["frame" ], FK4 )
258+ fobstime = self ._get_time_instance (hk_field ,
259+ besselian = besselian )
260+ # FK4 class has an obstime attribute which must be set at instanciation time
261+ if besselian :
262+ kwargs ["frame" ] = FK4 (equinox = kwargs ["frame" ].equinox , obstime = fobstime )
263+ # This is not the case for any other space frames
184264 else :
185- kwargs [value ] = fobstime
186- # Convert the parallax (mango) into a distance
187- elif value == "distance" :
188- kwargs [value ] = (hk_field ["value" ]
189- * u .Unit (hk_field ["unit" ]).to (u .parsec , equivalencies = u .parallax ()))
190- kwargs [value ] = kwargs [value ] * u .parsec
191- elif "unit" in hk_field and hk_field ["unit" ]:
192- kwargs [value ] = hk_field ["value" ] * u .Unit (hk_field ["unit" ])
193- else :
194- kwargs [value ] = hk_field ["value" ]
265+ kwargs [skycoord_field ] = fobstime
266+ # ignore not set parameters
267+ elif (hk_value := hk_field ["value" ]) is not None :
268+ # Convert the parallax (mango) into a distance
269+ if skycoord_field == "distance" :
270+ kwargs [skycoord_field ] = (hk_value
271+ * u .Unit (hk_field ["unit" ]).to (u .parsec , equivalencies = u .parallax ()))
272+ kwargs [skycoord_field ] = kwargs [skycoord_field ] * u .parsec
273+ elif "unit" in hk_field and hk_field ["unit" ]:
274+ kwargs [skycoord_field ] = hk_value * u .Unit (hk_field ["unit" ])
275+ else :
276+ kwargs [skycoord_field ] = hk_value
277+
195278 return SkyCoord (** kwargs )
0 commit comments