1 """
2 Converting ASTs to/from STC-X.
3
4 The basic idea for conversion to STC-X is that for every ASTNode in dm, there
5 is a serialize_<classname> function returning some xmlstan. In general
6 they should handle the case when their argument is None and return None
7 in that case.
8
9 Traversal is done manually (i.e., by each serialize_X method) rather than
10 globally since the children in the AST may not have the right order to
11 keep XSD happy, and also since ASTs are actually a bit more complicated
12 than trees (e.g., coordinate frames usually have multiple parents).
13 """
14
15
16
17
18
19
20
21 import itertools
22
23 from gavo import utils
24 from gavo.stc import common
25 from gavo.stc import dm
26 from gavo.stc.stcx import STC
27
28
30 """adds a synthetic id attribute to node unless it's already
31 there.
32 """
33 if not hasattr(node, "id") or node.id is None:
34 node.id = utils.intToFunnyWord(id(node))
35
36
38 if val is None:
39 return None
40 elif isinstance(val, common.ColRef):
41 return val
42 else:
43 return str(val)
44
45
53
54
56 if isinstance(val, common.ColRef) or val is None:
57 return val
58 return str(val)
59
60
62 """returns the STC element elName or raises an STCValueError if
63 it does not exist.
64
65 itemDesc is used in the error message. This is a helper for
66 concise notation of reference frames.
67 """
68 if elName is None:
69 elName = "UNKNOWNFrame"
70 try:
71 return getattr(STC, elName)
72 except AttributeError:
73 raise common.STCValueError("No such %s: %s"%(itemDesc, elName))
74
75
76 -class Context(object):
77 """is a generation context.
78
79 It is used to pass around genration-related information. Right now,
80 that's primarily the root node.
81 """
82 - def __init__(self, rootNode):
83 self.rootNode = rootNode
84
85 - def getPosForInterval(self, node):
86 return getattr(self.rootNode, node.cType.posAttr)
87
88
89
90
92 try:
93 return getattr(STC, node.standardOrigin or "UNKNOWNRefPos")[
94 STC.PlanetaryEphem[node.planetaryEphemeris]]
95 except AttributeError:
96 raise common.STCValueError(
97 "No such standard origin: %s"%node.standardOrigin)
98
99
101
102
103 if eq is None:
104 return None
105 res = eq[0]+"%.3f"%float(eq[1:])
106
107 if res.endswith("00"):
108 res = res[:-2]
109 return res
110
121
122
131
139
148
160
161
162
163
164
166 """returns the items of valSeq as children of element, mapped with mapper.
167 """
168 if valSeq is None:
169 return []
170 return [element[mapper(v)] for v in valSeq]
171
172
174 if node is None:
175 return
176 cooClass, radiusClass, matrixClass = wiggles
177 if isinstance(node, dm.CooWiggle):
178 return _wrapValues(cooClass, node.values, serializer),
179 elif isinstance(node, dm.RadiusWiggle):
180 return [radiusClass[strOrNull(r)] for r in node.radii]
181 elif isinstance(node, dm.MatrixWiggle):
182 return [matrixClass[_wrapMatrix(m, strOrNull)] for m in node.matrices]
183 else:
184 raise common.STCValueError("Cannot serialize %s errors to STC-X"%
185 node.__class__.__name__)
186
187
188 wiggleClasses = {
189 "error": [
190 (STC.Error, None, None),
191 (STC.Error2, STC.Error2Radius, STC.Error2Matrix),
192 (STC.Error3, STC.Error3Radius, STC.Error3Matrix),],
193 "resolution": [
194 (STC.Resolution, None, None),
195 (STC.Resolution2, STC.Resolution2Radius, STC.Resolution2Matrix),
196 (STC.Resolution3, STC.Resolution3Radius, STC.Resolution3Matrix),],
197 "size": [
198 (STC.Size, None, None),
199 (STC.Size2, STC.Size2Radius, STC.Size2Matrix),
200 (STC.Size3, STC.Size3Radius, STC.Size3Matrix),],
201 "pixSize": [
202 (STC.PixSize, None, None),
203 (STC.PixSize2, STC.PixSize2Radius, STC.PixSize2Matrix),
204 (STC.PixSize3, STC.PixSize3Radius, STC.PixSize3Matrix),],
205 }
206
207
209 """returns a serializer returning a coordinate cooClass.
210
211 This will only work for 1-dimensional coordinates. valueSerializer
212 is a function taking the coordinate's value and returning some
213 xmlstan.
214 """
215 def serialize(node, context):
216 res = cooClass[
217 valueSerializer(node.value),
218 _wrapValues(STC.Error, getattr(node.error, "values", ())),
219 _wrapValues(STC.Resolution, getattr(node.resolution, "values", ())),
220 _wrapValues(STC.PixSize, getattr(node.pixSize, "values", ())),
221 ]
222 if not res.shouldBeSkipped():
223 return res(unit=node.unit,
224 vel_time_unit=getattr(node, "velTimeUnit", None),
225 frame_id=node.frame.id)
226 return serialize
227
228 serialize_TimeCoo = _make1DSerializer(STC.Time,
229 lambda value: STC.TimeInstant[STC.ISOTime[isoformatOrNull(value)]])
230 serialize_RedshiftCoo = _make1DSerializer(STC.Redshift,
231 lambda value: STC.Value[strOrNull(value)])
232 serialize_SpectralCoo = _make1DSerializer(STC.Spectral,
233 lambda value: STC.Value[strOrNull(value)])
234
235
236 _nones = (None, None, None)
237
238
240 if not val:
241 return
242 return _refOrStr(val[0])
243
245 if not val:
246 return
247 if isinstance(val, common.ColRef):
248 return val
249 return [STC.C1(pos_unit=unit[0], vel_time_unit=timeUnit[0])[val[0]],
250 STC.C2(pos_unit=unit[1], vel_time_unit=timeUnit[1])[val[1]]]
251
253 if not val:
254 return
255 if isinstance(val, common.ColRef):
256 return val
257 return [STC.C1(pos_unit=unit[0], vel_time_unit=timeUnit[0])[val[0]],
258 STC.C2(pos_unit=unit[1], vel_time_unit=timeUnit[1])[val[1]],
259 STC.C3(pos_unit=unit[2], vel_time_unit=timeUnit[2])[val[2]]]
260
262 for rowInd, row in enumerate(val):
263 for colInd, col in enumerate(row):
264 yield getattr(STC, "M%d%d"%(rowInd, colInd))[serializer(col)]
265
266 _spatialPosClasses = (
267 (STC.Position1D, STC.Value, _wrap1D),
268 (STC.Position2D, STC.Value2, _wrap2D),
269 (STC.Position3D, STC.Value3, _wrap3D),
270 )
271 _velocityPosClasses = (
272 (STC.Velocity1D, STC.Value, _wrap1D),
273 (STC.Velocity2D, STC.Value2, _wrap2D),
274 (STC.Velocity3D, STC.Value3, _wrap3D),
275 )
276
278 clsArgs, cooArgs = {}, {}
279 if node.unit:
280 if len(set(node.unit))==1:
281 clsArgs["unit"] = node.unit[0]
282 elif node.unit:
283 cooArgs["unit"] = node.unit
284 if hasattr(node, "velTimeUnit"):
285 if len(set(node.velTimeUnit))==1:
286 clsArgs["vel_time_unit"] = node.velTimeUnit[0]
287 elif node.unit:
288 cooArgs["timeUnit"] = node.velTimeUnit
289 return clsArgs, cooArgs
290
291
293 """serializes a spatial coordinate.
294
295 This is quite messy since the concrete choice of elements depends on
296 the coordinate frame.
297 """
298 def serialize(node, context):
299 if node.frame.nDim is None and node.value:
300 dimInd = len(node.value)-1
301 else:
302 dimInd = node.frame.nDim-1
303 coo, val, serializer = stcClasses[dimInd]
304 clsArgs, cooArgs = _getSpatialUnits(node)
305
306 valueStan = val[serializer(node.value, **cooArgs)]
307 res = coo[
308 valueStan,
309 [_serialize_Wiggle(getattr(node, wiggleType),
310 serializer, wiggleClasses[wiggleType][dimInd])
311 for wiggleType in ["error", "resolution", "size", "pixSize"]],
312 ]
313 if node.epoch:
314 res[STC.Epoch(yearDef=node.yearDef)[_refOrStr(node.epoch)]]
315 if not res.shouldBeSkipped():
316 return res(frame_id=node.frame.id, **clsArgs)
317 return serialize
318
319 serialize_SpaceCoo = _makeSpatialCooSerializer(_spatialPosClasses)
320 serialize_VelocityCoo = _makeSpatialCooSerializer(_velocityPosClasses)
321
322
323
324
327 """returns a serializer returning stan for a coordinate interval.
328
329 This will only work for 1-dimensional coordinates. valueSerializer
330 is a function taking the coordinate's value and returning some
331 xmlstan.
332
333 Currently, error, resolution, and pixSize information is discarded
334 for lack of a place to put them.
335 """
336 def serialize(node, context):
337 posNode = context.getPosForInterval(node)
338 if isinstance(node.frame, dm.TimeFrame):
339 unit = None
340 else:
341 unit = posNode.unit
342 return intervClass(unit=unit,
343 vel_time_unit=getattr(posNode, "velTimeUnit", None),
344 frame_id=node.frame.id, fill_factor=strOrNull(node.fillFactor))[
345 lowerClass[valueSerializer(node.lowerLimit)],
346 upperClass[valueSerializer(node.upperLimit)],
347 ]
348 return serialize
349
350
351 serialize_TimeInterval = _make1DIntervalSerializer(STC.TimeInterval,
352 STC.StartTime, STC.StopTime, lambda val: STC.ISOTime[isoformatOrNull(val)])
353 serialize_SpectralInterval = _make1DIntervalSerializer(STC.SpectralInterval,
354 STC.LoLimit, STC.HiLimit, strOrNull)
355 serialize_RedshiftInterval = _make1DIntervalSerializer(STC.RedshiftInterval,
356 STC.LoLimit, STC.HiLimit, strOrNull)
357
358
359 _posIntervalClasses = [
360 (STC.PositionScalarInterval, STC.LoLimit, STC.HiLimit, _wrap1D),
361 (STC.Position2VecInterval, STC.LoLimit2Vec, STC.HiLimit2Vec, _wrap2D),
362 (STC.Position3VecInterval, STC.LoLimit3Vec, STC.HiLimit3Vec, _wrap3D),]
363 _velIntervalClasses = [
364 (STC.VelocityScalarInterval, STC.LoLimit, STC.HiLimit, _wrap1D),
365 (STC.Velocity2VecInterval, STC.LoLimit2Vec, STC.HiLimit2Vec, _wrap2D),
366 (STC.Velocity3VecInterval, STC.LoLimit3Vec, STC.HiLimit3Vec, _wrap3D),]
367
369 def serialize(node, context):
370 intervClass, lowerClass, upperClass, valueSerializer = \
371 stcClasses[node.frame.nDim-1]
372 posNode = context.getPosForInterval(node)
373 clsArgs, cooArgs = _getSpatialUnits(posNode)
374
375
376
377
378
379
380
381 return intervClass(frame_id=node.frame.id, fill_factor=node.fillFactor,
382 **clsArgs)[
383 lowerClass[valueSerializer(node.lowerLimit, **cooArgs)],
384 upperClass[valueSerializer(node.upperLimit, **cooArgs)],
385 ]
386 return serialize
387
388 serialize_SpaceInterval = _makeSpatialIntervalSerializer(_posIntervalClasses)
389 serialize_VelocityInterval = _makeSpatialIntervalSerializer(_velIntervalClasses)
390
391
392
393
394
396 if sampleValue is None:
397 return None
398 if isinstance(sampleValue, float):
399 return 1
400 else:
401 return len(sampleValue)
402
403
405 buildArgs = _getSpatialUnits(context.getPosForInterval(node))[0]
406 res = cls(frame_id=getattr(node.frame, "id", None),
407 fill_factor=strOrNull(node.fillFactor), **buildArgs)
408 return res
409
410
412 return _makeBaseGeometry(STC.AllSky, node, context)
413
415
416
417 if node.geoColRef:
418 return STC.Circle[node.geoColRef]
419 nDim = _getDim(node.center)
420 if nDim==2:
421 return _makeBaseGeometry(STC.Circle, node, context)[
422 STC.Center[_wrap2D(node.center)],
423 STC.Radius[node.radius],
424 ]
425 elif nDim==3:
426 return _makeBaseGeometry(STC.Sphere, node, context)[
427 STC.Radius[node.radius],
428 STC.Center[_wrap3D(node.center)],
429 ]
430 else:
431 raise common.STCValueError("Spheres are only defined in 2 and 3D")
432
433
447
448
450 if node.geoColRef:
451 return STC.Box[node.geoColRef]
452 if _getDim(node.center)!=2:
453 raise common.STCValueError("Boxes are only available in 2D")
454 return _makeBaseGeometry(STC.Box, node, context)[
455 STC.Center[_wrap2D(node.center)],
456 STC.Size[_wrap2D(node.boxsize)]]
457
458
460 if node.geoColRef:
461 return STC.Polygon[node.geoColRef]
462 if node.vertices and _getDim(node.vertices[0])!=2:
463 raise common.STCValueError("Polygons are only available in 2D")
464 return _makeBaseGeometry(STC.Polygon, node, context)[
465 [STC.Vertex[STC.Position[_wrap2D(v)]] for v in node.vertices]]
466
467
474
475
477 if len(node.children)<2:
478 return _nodeToStan(node)
479 return {"Union": STC.Union, "Intersection": STC.Intersection}[
480 node.__class__.__name__][[_nodeToStan(c, context) for c in node.children]]
481
482
483 serialize_Union = serialize_Intersection = serialize_MultiCompound
484
486 if len(node.children)!=2:
487 raise common.STCValueError("Difference is only supported with two operands")
488 op1 = _nodeToStan(node.children[0], context)
489 op2 = _nodeToStan(node.children[1], context)
490
491
492 op2.name_ = op2.name_+"2"
493 return STC.Difference[op1, op2]
494
496 if len(node.children)!=1:
497 raise common.STCValueError("Not is only supported with one operand")
498 return STC.Negation[_nodeToStan(node.children[0], context)]
499
500
501
502
504 """serializes the areas contained in rootNode.
505
506 This requires all kinds of insane special handling.
507 """
508 if not rootNode.areas:
509 return
510 elif len(rootNode.areas)==1:
511 return _nodeToStan(rootNode.areas[0], context)
512 else:
513 return STC.Region[
514 STC.Union[
515 [_nodeToStan(n, context) for n in rootNode.areas]]]
516
517
519 """returns xmlstan for whatever is in astNode.
520 """
521 return globals()["serialize_"+astNode.__class__.__name__](astNode, context)
522
523
525 """returns xmlstan for an AST node.
526 """
527 context = Context(astNode)
528 return _nodeToStan(astNode, context)
529
530
532 """returns STC stan for the AST rootNode wrapped in the stcRoot element.
533
534 The first coordinate system defined in the AST is always used for
535 the embedded coordinates and areas.
536 """
537 context = Context(rootNode)
538 stcRoot[_nodeToStan(rootNode.astroSystem, context)]
539 return stcRoot[_nodeToStan(rootNode.astroSystem, context),
540 STC.AstroCoords(coord_system_id=rootNode.astroSystem.id)[
541 [_nodeToStan(n, context) for n in [rootNode.time,
542 rootNode.place, rootNode.velocity, rootNode.freq,
543 rootNode.redshift] if n]
544 ],
545 STC.AstroCoordArea(coord_system_id=rootNode.astroSystem.id)[
546 [_nodeToStan(n, context) for n in rootNode.timeAs],
547 makeAreas(rootNode, context),
548 [_nodeToStan(n, context) for n in
549 itertools.chain(rootNode.velocityAs, rootNode.freqAs,
550 rootNode.redshiftAs)]],
551 ]
552
553
557