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

Source Code for Module gavo.stc.stcsgen

  1  """ 
  2  Converting ASTs to STC-S. 
  3   
  4  The strategy here is to first generate an STCS CST, remove defaults from it 
  5  and then flatten out the whole thing. 
  6   
  7  The AST serializers here either return a dictionary, which is then 
  8  updated to the current node's dictionary, or a tuple of key and value, 
  9  which is then added to the current dictionary. 
 10  """ 
 11   
 12  #c Copyright 2008-2019, the GAVO project 
 13  #c 
 14  #c This program is free software, covered by the GNU GPL.  See the 
 15  #c COPYING file in the source distribution. 
 16   
 17   
 18  import datetime 
 19  import itertools 
 20   
 21  from gavo import utils 
 22  from gavo.stc import common 
 23  from gavo.stc import dm 
 24  from gavo.stc import stcs 
 25  from gavo.stc import syslib 
 26   
 27   
28 -def _combine(*dicts):
29 """updates the first dictionary with all further ones and returns it. 30 31 If duplicate keys exist, later arguments will overwrite values set 32 by earlier arguments. 33 """ 34 res = dicts[0] 35 for d in dicts[1:]: 36 res.update(d) 37 return res
38 39 40 ############## Reference Frames to CST 41
42 -def refPosToCST(node):
43 return { 44 "refpos": node.standardOrigin, 45 "planetaryEphemeris": node.planetaryEphemeris,}
46 47 stcsFlavors = { 48 (2, "SPHERICAL"): "SPHER2", 49 (3, "SPHERICAL"): "SPHER3", 50 (3, "UNITSPHERE"): "UNITSPHER", 51 (1, "CARTESIAN"): "CART1", 52 (2, "CARTESIAN"): "CART2", 53 (3, "CARTESIAN"): "CART3", 54 } 55
56 -def _computeFlavor(node):
57 try: 58 return stcsFlavors[(node.nDim, node.flavor)] 59 except KeyError: 60 raise common.STCValueError("Coordinate Frame %s cannot" 61 " be represented it STC-S"%node)
62 63 # Simple translations of STC-X reference frame literals to STC-S 64 _frameTrans = { 65 "GALACTIC_II": "GALACTIC", 66 None: "UNKNOWNFrame", 67 } 68
69 -def _spaceFrameToCST(node):
70 if node is None: 71 return {"frame": "UNKNOWNFrame"} 72 73 frame = node.refFrame 74 frame = _frameTrans.get(frame, frame) 75 return _combine({ 76 "flavor": _computeFlavor(node), 77 "frame": frame, 78 "equinox": node.equinox,}, 79 refPosToCST(node.refPos))
80
81 -def _timeFrameToCST(node):
82 if node is None: 83 return {} 84 return _combine({ 85 "timescale": node.timeScale,}, 86 refPosToCST(node.refPos))
87
88 -def _spectralFrameToCST(node):
89 if node is None: 90 return {} 91 return refPosToCST(node.refPos)
92
93 -def _redshiftFrameToCST(node):
94 if node is None: 95 return {} 96 return _combine({ 97 "redshiftType": node.type, 98 "dopplerdef": node.dopplerDef,}, 99 refPosToCST(node.refPos))
100 101 102 ############### Coordinates to CST 103
104 -def _wiggleToCST(node, nDim):
105 if node is None: 106 return 107 if isinstance(node, dm.CooWiggle): 108 return node.values 109 elif isinstance(node, dm.RadiusWiggle): 110 return tuple((r,)*nDim for r in node.radii) 111 else: 112 raise common.STCValueError("Cannot serialize %s wiggles into STC-S"% 113 node.__class__.__name__)
114 115
116 -def _makeCooTreeMapper(cooType):
117 """returns a function returning a CST fragment for a coordinate. 118 """ 119 def toCST(node): 120 if node.frame is None: # no frame, no coordinates. 121 return {} 122 nDim = node.frame.nDim 123 return { 124 "error": _wiggleToCST(node.error, nDim), 125 "resolution": _wiggleToCST(node.resolution, nDim), 126 "pixSize": _wiggleToCST(node.pixSize, nDim), 127 "unit": node.getUnitString(), 128 "type": cooType, 129 "pos": node.value or None,}
130 return toCST 131 132
133 -def _makeIntervalCoos(node):
134 res = {} 135 if node.lowerLimit or node.upperLimit: 136 res["coos"] = [c for c in (node.lowerLimit, node.upperLimit) 137 if c is not None] 138 return res
139 140
141 -def _makeTimeIntervalCoos(node):
142 # Special-cased since these have (Start|Stop)Time 143 if node.lowerLimit and node.upperLimit: 144 type, coos = "TimeInterval", (node.lowerLimit, node.upperLimit) 145 elif node.lowerLimit: 146 type, coos = "StartTime", (node.lowerLimit,) 147 elif node.upperLimit: 148 type, coos = "StopTime", (node.upperLimit,) 149 else: 150 type, coos = "TimeInterval", () 151 return { 152 "type": type, 153 "coos": coos}
154 155
156 -def _makeAreaTreeMapper(areaType, cooMaker=_makeIntervalCoos):
157 """returns a CST fragment for an area. 158 159 areaType this CST type of the node returned, cooMaker is a function 160 that receives the node and returns a dictionary containing at least 161 a coos key. It can set other keys as well (e.g. 162 _makeTimeIntervalCoos needs to override the type key). 163 """ 164 def toCST(node): 165 return _combine({ 166 "fillfactor": node.fillFactor, 167 "type": areaType}, 168 cooMaker(node))
169 return toCST 170 171
172 -def _makePhraseTreeMapper(cooMapper, areaMapper, frameMapper, 173 getASTItems):
174 """returns a mapper building a CST fragment for a subphrase. 175 176 cooMapper and areaMapper are functions returning CST fragments 177 for coordinates and areas of this type, respectively. 178 179 getASTItems is a function that receives the AST root and has to 180 return either None (no matching items found in AST) or a pair 181 of coordinate and area, where area may be None. Use _makeASTItemsGetter 182 to build these functions. 183 184 The function returned expects the root of the AST as argument. 185 """ 186 def toCST(astRoot): 187 items = getASTItems(astRoot) 188 if items is None: 189 return {} 190 coo, area = items 191 areaKeys = {} 192 if area: 193 areaKeys = areaMapper(area) 194 cooKeys = cooMapper(coo) 195 frame = coo.frame or area.frame 196 return _combine(cooKeys, 197 areaKeys, # area keys come later to override cst key "type". 198 frameMapper(frame))
199 return toCST 200 201
202 -def _makeASTItemsGetter(cooName, areaName):
203 """returns a function that extracts coordinates and areas of 204 a certain type from an AST. 205 206 The function does all kinds of sanity checks and raises STCValueErrors 207 if those fail. 208 209 If all goes well, it will return a pair coo, area. coo is always 210 non-None, area may be None. 211 """ 212 def getASTItems(astRoot): 213 areas, coo = getattr(astRoot, areaName), getattr(astRoot, cooName) 214 if not areas and not coo: 215 return None 216 if len(areas)>1: 217 raise common.STCValueError("STC-S does not support more than one area" 218 " but %s has length %d"%(areaName, len(areas))) 219 if areas: 220 area = areas[0] 221 else: 222 area = None 223 return coo, area
224 return getASTItems 225 226
227 -def _spatialCooToCST(node, getBase=_makeCooTreeMapper("Position")):
228 if node.frame is None: 229 return {} 230 cstNode = getBase(node) 231 cstNode["size"] = _wiggleToCST(node.size, node.frame.nDim) 232 if node.epoch is not None: 233 yearDef = node.yearDef 234 if yearDef is None: 235 yearDef = "J" 236 cstNode["epoch"] = yearDef+str(node.epoch) 237 return cstNode
238 239 240 _timeToCST = _makePhraseTreeMapper( 241 _makeCooTreeMapper("Time"), 242 _makeAreaTreeMapper("TimeInterval", _makeTimeIntervalCoos), 243 _timeFrameToCST, 244 _makeASTItemsGetter("time", "timeAs")) 245 _simpleSpatialToCST = _makePhraseTreeMapper( 246 _spatialCooToCST, 247 _makeAreaTreeMapper("PositionInterval"), 248 _spaceFrameToCST, 249 _makeASTItemsGetter("place", "areas")) 250 _spectralToCST = _makePhraseTreeMapper( 251 _makeCooTreeMapper("Spectral"), 252 _makeAreaTreeMapper("SpectralInterval"), 253 _spectralFrameToCST, 254 _makeASTItemsGetter("freq", "freqAs")) 255 _redshiftToCST = _makePhraseTreeMapper( 256 _makeCooTreeMapper("Redshift"), 257 _makeAreaTreeMapper("RedshiftInterval"), 258 _redshiftFrameToCST, 259 _makeASTItemsGetter("redshift", "redshiftAs")) 260 _velocityToCST = _makePhraseTreeMapper( 261 _makeCooTreeMapper("VelocityInterval"), 262 _makeAreaTreeMapper("VelocityInterval"), 263 lambda _: {}, # Frame provided by embedding position 264 _makeASTItemsGetter("velocity", "velocityAs")) 265
266 -def _sysIdToCST(astRoot):
267 if astRoot.astroSystem.libraryId: 268 return syslib.stripIVOID(astRoot.astroSystem.libraryId)
269 270
271 -def _makeAllSkyCoos(node):
272 return {"geoCoos": ()}
273
274 -def _makeCircleCoos(node):
275 return {"geoCoos": node.center+(node.radius,)}
276
277 -def _makeEllipseCoos(node):
278 return {"geoCoos": node.center+(node.smajAxis, node.sminAxis, node.posAngle)}
279
280 -def _makeBoxCoos(node):
281 return {"geoCoos": node.center+node.boxsize}
282
283 -def _makePolygonCoos(node):
284 return {"geoCoos": tuple(itertools.chain(*node.vertices))}
285
286 -def _makeConvexCoos(node):
287 return {"geoCoos": tuple(itertools.chain(*node.vectors))}
288
289 -def _makeSpaceIntervalCoos(node):
290 return {"geoCoos": node.lowerLimit+node.upperLimit}
291 292 _compoundGeos = ["Union", "Difference", "Intersection", "Not"] 293
294 -def _makeUnionCoos(node):
295 children = [] 296 for c in node.children: 297 nodeName = c.__class__.__name__ 298 base = {"subtype": nodeName} 299 if c in _compoundGeos: 300 children.append(_combine(base, _makeUnionCoos(c))) 301 else: 302 children.append(_combine(base, 303 globals()["_make%sCoos"%nodeName](c))) 304 return {"children": children}
305 306 _makeDifferenceCoos = _makeIntersectionCoos = _makeNotCoos = _makeUnionCoos 307 308 _geometryMappers = dict([(n, _makePhraseTreeMapper( 309 _spatialCooToCST, 310 _makeAreaTreeMapper(n, globals()["_make%sCoos"%n]), 311 _spaceFrameToCST, 312 _makeASTItemsGetter("place", "areas"))) 313 for n in ["AllSky", "Circle", "Ellipse", "Box", "Polygon", "Convex"]+ 314 _compoundGeos]) 315
316 -def _spatialToCST(astRoot):
317 args = {} 318 velocityArgs = _velocityToCST(astRoot) 319 if velocityArgs: 320 args = {"velocity": velocityArgs} 321 node = (astRoot.areas and astRoot.areas[0]) or astRoot.place 322 323 if not node: 324 # This means we have neither a place nor a geometry but still 325 # want a frame (e.g., velocity only) 326 if args: 327 args.update(_spaceFrameToCST(astRoot.astroSystem.spaceFrame)) 328 args["type"] = "Position" 329 elif isinstance(node, (dm.SpaceCoo, dm.SpaceInterval)): 330 args.update(_simpleSpatialToCST(astRoot)) 331 else: # Ok, it's a geometry 332 args.update(_geometryMappers[node.__class__.__name__](astRoot)) 333 return args
334 335 336 ############## Flattening of the CST 337
338 -def _joinWithNull(strList):
339 res = " ".join(s for s in strList if s is not None) 340 if not res: 341 return None 342 return res
343 344
345 -def _joinKeysWithNull(node, kwList, flatteners):
346 """returns a string made up of the non-null values in kwList in node. 347 348 To make things a bit more flexible, you can give lists in kwList. 349 Their elements will be inserted into the result as-is. 350 351 Flatteners is a dictionary mapping keys from kwList to functions 352 353 flat(val, node) -> string 354 355 that turn a value for the keyword to a complete string. 356 _makeKeywordFlattener and _makeNoKeywordFlattener generate such functions. 357 """ 358 res = [] 359 for key in kwList: 360 if isinstance(key, list): 361 res.extend(key) 362 elif key not in node: 363 pass 364 elif key in flatteners: 365 res.append(flatteners[key](node[key], node)) 366 else: 367 res.append(node[key]) 368 return _joinWithNull(res)
369 370
371 -def _flattenValue(val, node=None):
372 """returns a sensible STC-S string representation for many sorts of 373 values, dispatched on their type. 374 375 This function can be used as a flattener. 376 """ 377 if val is None: 378 return "" 379 elif isinstance(val, basestring): 380 return str(val) 381 elif isinstance(val, (int, float)): 382 return str(val) 383 elif isinstance(val, (list, tuple)): 384 return " ".join(_flattenValue(v) for v in val) 385 elif isinstance(val, datetime.datetime): 386 return val.isoformat() 387 elif isinstance(val, common.ColRef): 388 return '"%s"'%val.dest 389 else: 390 raise common.STCValueError("Cannot serialize %r to STC-S"%val)
391 392
393 -def _makePosValueFlattener(key):
394 """returns a flattener for position values of type key. 395 396 The trick is to suppress key when the node already represents a position 397 type. Thus, we generate "Time 2" rather than Time Time 2 analogous to 398 "TimeInterval [...] Time 2". 399 """ 400 def flattenPosition(val, node): 401 if val is not None: 402 if node["type"]==key: 403 return _flattenValue(val) 404 else: 405 return "%s %s"%(key, _flattenValue(val))
406 return flattenPosition 407 408
409 -def _makeKeywordFlattener(keyword):
410 """returns a function returning a flattened value with keyword in front. 411 """ 412 if keyword: 413 fmtStr = "%s %%s"%keyword 414 else: 415 fmtStr = "%s" 416 def flatten(val, node): 417 if val is not None and val!=(): 418 return fmtStr%(_flattenValue(val))
419 return flatten 420 421
422 -def _flattenRefPos(val, node):
423 return _joinWithNull([node["refpos"], node["planetaryEphemeris"]])
424 425 426 # Keywords for items common to all coordinates. 427 _commonFlatteners = { 428 "fillfactor": _makeKeywordFlattener("fillfactor"), 429 "unit": _makeKeywordFlattener("unit"), 430 "error": _makeKeywordFlattener("Error"), 431 "resolution": _makeKeywordFlattener("Resolution"), 432 "size": _makeKeywordFlattener("Size"), 433 "pixSize": _makeKeywordFlattener("PixSize"), 434 "epoch": _makeKeywordFlattener("Epoch"), 435 "coos": _flattenValue, 436 "refpos": _flattenRefPos, 437 } 438 439
440 -def _make1DCooFlattener(posKey, frameKeys):
441 """returns a flattener for 1-D coordinates (time, spectral, redshift). 442 """ 443 flatteners = { 444 "pos": _makePosValueFlattener(posKey), 445 } 446 flatteners.update(_commonFlatteners) 447 keyList = ["type", "fillfactor"]+frameKeys+["coos", "pos", 448 "unit", "error", "resolution", "pixSize"] 449 def flatten(node): 450 return _joinKeysWithNull(node, keyList, flatteners)
451 return flatten 452 453
454 -def _makeVelocityFlattener():
455 """returns a flattener for velocities. 456 457 This is only used by _makePositionFlattener, since velocity is a subclause. 458 """ 459 flatteners = { 460 "pos": _makePosValueFlattener("VelocityInterval"), 461 } 462 flatteners.update(_commonFlatteners) 463 464 def flattenVelocity(val, node): 465 """custom flattener for velocities, used by _flattenPosition. 466 """ 467 res = [] 468 if val: 469 if val.get("coos") is not None: 470 res.extend(["VelocityInterval", _joinKeysWithNull(val, 471 ["fillfactor", "coos"], flatteners)]) 472 if val.get("pos") is not None: 473 res.extend(["Velocity", _joinKeysWithNull(val, ["pos"], flatteners)]) 474 if not res: 475 res.append("Velocity") 476 res.append(_joinKeysWithNull(val, 477 ["unit", "error", "resolution", "size", "pixSize"], flatteners)) 478 return _joinWithNull(res)
479 480 return flattenVelocity 481 482
483 -def _makeSpatialFlattener():
484 def flattenCompoundChildren(childList, node): 485 """custom flattener for compounds (i.e., and, or, not) 486 """ 487 res = [] 488 for c in childList: 489 if "geoCoos" in c: # it's an atomic geometry 490 res.append("%s %s"%( 491 c["subtype"], _flattenValue(c["geoCoos"]))) 492 else: # it's a compound 493 res.append("%s %s"%(c["subtype"], 494 flattenCompoundChildren(c["children"], node))) 495 if "geoCoos" in node: # it's atomic, no parens required 496 return " ".join(res) 497 else: 498 return "(%s)"%(" ".join(res))
499 500 flatteners = { 501 "pos": _makePosValueFlattener("Position"), 502 "geoCoos": _makeKeywordFlattener(""), 503 "velocity": _makeVelocityFlattener(), 504 "children": flattenCompoundChildren, 505 } 506 flatteners.update(_commonFlatteners) 507 508 def flattenPosition(node): 509 """returns an STC-S representation of position and velocity. 510 """ 511 return _joinKeysWithNull(node, ["type", "fillfactor", "frame", 512 "equinox", "refpos", "flavor", "epoch", "coos", "geoCoos", "children", 513 "pos", "unit", "error", "resolution", "size", "pixSize", "velocity"], 514 flatteners) 515 516 return flattenPosition 517 518 519 # generate top-level flatteners 520 521 _flattenTime = _make1DCooFlattener("Time", ["timescale", "refpos"]) 522 _flattenSpectral = _make1DCooFlattener("Spectral", ["refpos"]) 523 _flattenRedshift = _make1DCooFlattener("Redshift", 524 ["refpos", "redshiftType", "dopplerdef"]) 525 _flattenSpatial = _makeSpatialFlattener() 526
527 -def _flattenSystem(node):
528 if node: 529 return "System %s"%node
530 531
532 -def _flattenCST(cst):
533 """returns a flattened string for an STCS CST. 534 535 Flattening destroys the tree. 536 """ 537 return "\n".join([s for s in ( 538 _flattenTime(cst.get("time", {})), 539 _flattenSpatial(cst.get("space", {})), 540 _flattenSpectral(cst.get("spectral", {})), 541 _flattenRedshift(cst.get("redshift", {})), 542 _flattenSystem(cst.get("libSystem")),) 543 if s])
544 545
546 -def getSTCS(astRoot):
547 """returns an STC-S string for an AST. 548 """ 549 cst = stcs.removeDefaults({ 550 "time": _timeToCST(astRoot), 551 "space": _spatialToCST(astRoot), 552 "spectral": _spectralToCST(astRoot), 553 "redshift": _redshiftToCST(astRoot), 554 "libSystem": _sysIdToCST(astRoot), 555 }) 556 return _flattenCST(cst)
557 558
559 -def getSpatialSystem(astRoot):
560 """returns a phrase for the spatial system (for manual STC-S generation). 561 """ 562 cst = stcs.removeDefaults({"space": _spatialToCST(astRoot)}) 563 return _joinKeysWithNull(cst["space"], ["frame", 564 "equinox", "refpos", "flavor", "epoch"], _commonFlatteners)
565 566
567 -def _boxMapperFactory(colDesc):
568 """A factory for Boxes. 569 """ 570 if colDesc["dbtype"]!="box": 571 return 572 573 if colDesc.original.stc: 574 systemString = getSpatialSystem(colDesc.original.stc) 575 else: 576 systemString = "UNKNOWN" 577 578 def mapper(val): 579 if val is None: 580 return "" 581 else: 582 return "Box %s %s %s %s %s"%((systemString,)+val[0]+val[1])
583 colDesc["datatype"], colDesc["arraysize"] = "char", "*" 584 return mapper 585 utils.registerDefaultMF(_boxMapperFactory) 586