Package gavo :: Package stc :: Module stcxgen
[frames] | no frames]

Source Code for Module gavo.stc.stcxgen

  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  #c Copyright 2008-2019, the GAVO project 
 16  #c 
 17  #c This program is free software, covered by the GNU GPL.  See the 
 18  #c COPYING file in the source distribution. 
 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   
29 -def addId(node):
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
37 -def strOrNull(val):
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
46 -def isoformatOrNull(val):
47 if val is None: 48 return None 49 elif isinstance(val, common.ColRef): 50 return val 51 else: 52 return val.isoformat()
53 54
55 -def _refOrStr(val):
56 if isinstance(val, common.ColRef) or val is None: 57 return val 58 return str(val)
59 60
61 -def _getFromSTC(elName, itemDesc):
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 ############ Coordinate Systems 90
91 -def serialize_RefPos(node, context):
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
100 -def _fudgeEquinox(eq):
101 # Incredibly, the schema requires not more than three figures after the 102 # comma for equinox. Sigh. 103 if eq is None: 104 return None 105 res = eq[0]+"%.3f"%float(eq[1:]) 106 # Do some cosmetics 107 if res.endswith("00"): 108 res = res[:-2] 109 return res
110
111 -def serialize_SpaceFrame(node, context):
112 if node is None: return 113 addId(node) 114 return STC.SpaceFrame(id=node.id)[ 115 STC.Name[node.name], 116 _getFromSTC(node.refFrame, "reference frame")[ 117 STC.Equinox[_fudgeEquinox(node.equinox)]], 118 serialize_RefPos(node.refPos, context), 119 _getFromSTC(node.flavor, "coordinate flavor")( 120 coord_naxes=strOrNull(node.nDim))]
121 122
123 -def serialize_TimeFrame(node, context):
124 if node is None: return 125 addId(node) 126 return STC.TimeFrame(id=node.id)[ 127 STC.Name[node.name], 128 STC.TimeScale[node.timeScale], 129 serialize_RefPos(node.refPos, context), 130 ]
131
132 -def serialize_SpectralFrame(node, context):
133 if node is None: return 134 addId(node) 135 return STC.SpectralFrame(id=node.id)[ 136 STC.Name[node.name], 137 serialize_RefPos(node.refPos, context), 138 ]
139
140 -def serialize_RedshiftFrame(node, context):
141 if node is None: return 142 addId(node) 143 return STC.RedshiftFrame(id=node.id, value_type=node.type)[ 144 STC.Name[node.name], 145 STC.DopplerDefinition[node.dopplerDef], 146 serialize_RefPos(node.refPos, context), 147 ]
148
149 -def serialize_CoordSys(node, context):
150 addId(node) 151 if node.libraryId: 152 return STC.AstroCoordSystem(id=node.id, 153 href=node.libraryId) 154 else: 155 return STC.AstroCoordSystem(id=node.id)[ 156 serialize_TimeFrame(node.timeFrame, context), 157 serialize_SpaceFrame(node.spaceFrame, context), 158 serialize_SpectralFrame(node.spectralFrame, context), 159 serialize_RedshiftFrame(node.redshiftFrame, context),]
160 161 162 ############ Coordinates 163 164
165 -def _wrapValues(element, valSeq, mapper=strOrNull):
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
173 -def _serialize_Wiggle(node, serializer, wiggles):
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
208 -def _make1DSerializer(cooClass, valueSerializer):
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
239 -def _wrap1D(val, unit=_nones, timeUnit=_nones):
240 if not val: 241 return 242 return _refOrStr(val[0])
243
244 -def _wrap2D(val, unit=_nones, timeUnit=_nones):
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
252 -def _wrap3D(val, unit=_nones, timeUnit=_nones):
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
261 -def _wrapMatrix(val, serializer):
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
277 -def _getSpatialUnits(node):
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
292 -def _makeSpatialCooSerializer(stcClasses):
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 ############# Intervals 324
325 -def _make1DIntervalSerializer(intervClass, lowerClass, upperClass, 326 valueSerializer):
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 # time intervals have no units 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
368 -def _makeSpatialIntervalSerializer(stcClasses):
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 # check where we should stick these units at some point 376 # if len(set(posNode.unit))==1: 377 # unit = posNode.unit[0] 378 # elif posNode.unit: 379 # units = posNode.unit 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 ############# Geometries 393 394
395 -def _getDim(sampleValue):
396 if sampleValue is None: 397 return None 398 if isinstance(sampleValue, float): 399 return 1 400 else: 401 return len(sampleValue)
402 403
404 -def _makeBaseGeometry(cls, node, context):
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
411 -def serialize_AllSky(node, context):
412 return _makeBaseGeometry(STC.AllSky, node, context)
413
414 -def serialize_Circle(node, context):
415 # would you believe that the sequence of center and radius is swapped 416 # in sphere and circle? Oh boy. 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
434 -def serialize_Ellipse(node, context):
435 if node.geoColRef: 436 return STC.Ellipse[node.geoColRef] 437 if _getDim(node.center)==2: 438 cls, wrap = STC.Ellipse, _wrap2D 439 else: 440 raise common.STCValueError("Ellipses are only defined in 2D") 441 return _makeBaseGeometry(cls, node, context)[ 442 STC.Center[wrap(node.center)], 443 STC.SemiMajorAxis[node.smajAxis], 444 STC.SemiMinorAxis[node.sminAxis], 445 STC.PosAngle[node.posAngle], 446 ]
447 448
449 -def serialize_Box(node, context):
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
459 -def serialize_Polygon(node, context):
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
468 -def serialize_Convex(node, context):
469 if node.geoColRef: 470 return STC.Polygon[node.geoColRef] 471 return _makeBaseGeometry(STC.Convex, node, context)[ 472 [STC.Halfspace[STC.Vector[_wrap3D(v[:3])], STC.Offset[v[3]]] 473 for v in node.vectors]]
474 475
476 -def serialize_MultiCompound(node, context):
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
485 -def serialize_Difference(node, context):
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 # Banzai! To save myself the trouble of having all those icky *2 491 # elements around, I hack op2's name. 492 op2.name_ = op2.name_+"2" 493 return STC.Difference[op1, op2]
494
495 -def serialize_Not(node, context):
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 ############# Toplevel 502
503 -def makeAreas(rootNode, context):
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: # implicit union 513 return STC.Region[ 514 STC.Union[ 515 [_nodeToStan(n, context) for n in rootNode.areas]]]
516 517
518 -def _nodeToStan(astNode, context):
519 """returns xmlstan for whatever is in astNode. 520 """ 521 return globals()["serialize_"+astNode.__class__.__name__](astNode, context)
522 523
524 -def nodeToStan(astNode):
525 """returns xmlstan for an AST node. 526 """ 527 context = Context(astNode) 528 return _nodeToStan(astNode, context)
529 530
531 -def astToStan(rootNode, stcRoot):
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
554 -def getSTCXProfile(rootNode):
555 return astToStan(rootNode, STC.STCResourceProfile).render( 556 prefixForEmpty="stc")
557