1 """
2 The subset of STC proposed by the TAP spec.
3
4 Use this rather than the full-blown STC library for TAP and friends.
5 TAP's semi-sanitation of STC needs some special handling anyway,
6 and this is much faster than the full-blown mess.
7 """
8
9
10
11
12
13
14
15 from __future__ import print_function
16
17 from gavo import utils
18 from gavo.stc import common
19 from gavo.stc import stcsast
20 from gavo.utils import pgsphere
21
22
23 TAP_SYSTEMS = set(
24 ['ICRS', 'FK4', 'FK5', 'GALACTIC', 'RELOCATABLE', 'UNKNOWN', '', "BROKEN",
25 "UNKNOWNFrame"])
26
27
28 TAP_REFPOS = set(
29 ['BARYCENTER', 'GEOCENTER', 'HELIOCENTER', 'LSR', 'TOPOCENTER',
30 'RELOCATABLE', 'UNKNOWNREFPOS'])
31
32
33 TAP_FLAVORS = set(
34 ["CARTESIAN2", "CARTESIAN3", "SPHERICAL2"])
35
36
37
38
39 UNIVERSALLY_COMPATIBLE = set(['RELOCATABLE', 'UNKNOWN', '', "BROKEN", None])
40
41 TRANSFORMS = {
42
43 'FK4': (1.5651864333666516, -0.0048590552804904244, -1.5763681043529187),
44 'FK5': None,
45 'ICRS': None,
46 'GALACTIC': (1.3463560974407338, -1.0973190018372752, 0.57477052472873258),
47 }
54
57 """returns a pgsphere expression fragment to turn a pgsphere geometry
58 from fromSys to toSys.
59
60 fromSys and toSys are system designations like for TAP.
61
62 If the systems are "compatible" (i.e., no transformation is deemed necessary),
63 None is returned. An STCValueError is raised for incomprehensible system
64 specifications.
65 """
66 if (fromSys in UNIVERSALLY_COMPATIBLE
67 or toSys in UNIVERSALLY_COMPATIBLE):
68 return None
69 if fromSys=='ICRS':
70 template = "+strans(%.12f,%.12f,%.12f)"
71 angles = _getEulersFor(toSys)
72 elif toSys=='ICRS':
73 angles = _getEulersFor(fromSys)
74 template = "-strans(%.12f,%.12f,%.12f)"
75 else:
76 t1 = getPGSphereTrafo(fromSys, 'ICRS')
77 t2 = getPGSphereTrafo('ICRS', toSys)
78 return "%s%s"%(t1 or "", t2 or "")
79 if angles:
80 return template%angles
81 return None
82
85 """returns a tap system identifier for an STC AstroSystem.
86
87 This is stc.astroSystem.spaceFrame.refFrame if existing and
88 TAP-defined, UNKNOWN otherwise.
89 """
90 rf = None
91 if stcInstance.astroSystem and stcInstance.astroSystem.spaceFrame:
92 rf = stcInstance.astroSystem.spaceFrame.refFrame
93 if rf not in TAP_SYSTEMS:
94 return "UNKNOWN"
95 return rf
96
100 """returns an stc AST for a tap reference system identifier.
101
102 The tapIdentifier is any string in which the first item is the reference
103 system. Everything else is ignored (this is because it seems someone
104 intended additional specs like "BARYCENTER" to be legal, although
105 there really is nothing we can do about them).
106 """
107 if tapIdentifier:
108 tapIdentifier = utils.identifierPattern.findall(tapIdentifier)[0]
109 if tapIdentifier in ["BROKEN", '', "UNKNOWN"]:
110 tapIdentifier = "UNKNOWNFrame"
111 ast = stcsast.parseSTCS("Position %s"%tapIdentifier)
112 if tapIdentifier=='BROKEN':
113 ast.broken = True
114 return ast
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 -class GeomExpr(object):
136 """a sentinel object to be processed by morphers.
137 """
138 - def __init__(self, frame, operator, operands):
139 self.cooSys = frame
140 self.operator, self.operands = operator.upper(), operands
141
143 raise common.STCSParseError("Cannot serialize STC-S. Did you use"
144 " Union or Intersection outside of CONTAINS or INTERSECTS?")
145
147 if isinstance(operand, GeomExpr):
148 return operand.asLogic(template)
149 else:
150 return template%operand.asPgSphere()
151
153 if self.operator=="UNION":
154 logOp = " OR "
155 elif self.operator=="INTERSECTION":
156 logOp = " AND "
157 elif self.operator=="NOT":
158 return "NOT (%s)"%self._flatLogic(template, self.operands[0])
159 else:
160 raise NotImplementedError("No logic for operator '%s'"%self.operator)
161 return logOp.join(
162 '(%s)'%self._flatLogic(template, op) for op in self.operands)
163
169
175
178 if len(coords)!=4:
179 raise common.STCSParseError("STC-S boxes want four numbers.")
180 x,y,w,h = coords
181 return pgsphere.SPoly((
182 pgsphere.SPoint(x-w/2, y-h/2),
183 pgsphere.SPoint(x-w/2, y+h/2),
184 pgsphere.SPoint(x+w/2, y+h/2),
185 pgsphere.SPoint(x+w/2, y-h/2)))
186
194
197 """returns a utils.pgsphere instance from a match of simpleStatement in
198 the simple STCS parser below.
199 """
200 if match["flavor"] and match["flavor"].strip().upper()!="SPHERICAL2":
201 raise common.STCSParseError("Only SPHERICAL2 STC-S supported here")
202 refFrame = 'UnknownFrame'
203 if match["frame"]:
204 refFrame = match["frame"].strip()
205
206
207 handler = globals()["_make_pgs%s"%match["shape"].lower()]
208 res = handler(
209 tuple(float(s)*utils.DEG for s in match["coords"].strip().split() if s))
210 res.cooSys = refFrame
211 return res
212
214 return "|".join(sorted(strings, key=lambda s: -len(s)))
215
218 from pyparsing import (
219 Regex, CaselessKeyword, OneOrMore, Forward, Suppress,
220 Optional, ParseException, ParseSyntaxException)
221
222 with utils.pyparsingWhitechars(" \t\n\r"):
223 frameRE = _makeRE(TAP_SYSTEMS)
224 refposRE = _makeRE(TAP_REFPOS)
225 flavorRE = _makeRE(TAP_FLAVORS)
226 systemRE = (r"(?i)\s*"
227 r"(?P<frame>%s)?\s*"
228 r"(?P<refpos>%s)?\s*"
229 r"(?P<flavor>%s)?\s*")%(
230 frameRE, refposRE, flavorRE)
231 coordsRE = r"(?P<coords>(%s\s*)+)"%utils.floatRE
232
233 simpleStatement = Regex("(?i)\s*"
234 "(?P<shape>position|circle|box|polygon)"
235 +systemRE
236 +coordsRE)
237 simpleStatement.setName("STC-S geometry")
238 simpleStatement.addParseAction(lambda s,p,t: _makePgSphereInstance(t))
239 system = Regex(systemRE)
240 system.setName("STC-S system spec")
241 region = Forward()
242 notExpr = CaselessKeyword("NOT") + Suppress('(') + region + Suppress(')')
243 notExpr.addParseAction(lambda s,p,t: GeomExpr("UNKNOWN", "NOT", (t[1],)))
244 opExpr = (
245 (CaselessKeyword("UNION") | CaselessKeyword("INTERSECTION"))("op")
246 + Optional(Regex(frameRE))("frame")
247 + Optional(Regex(refposRE)) + Optional(Regex(flavorRE))
248 + Suppress("(")
249 + region + OneOrMore(region)
250 + Suppress(")"))
251 opExpr.addParseAction(
252 lambda s,p,t: GeomExpr(str(t["frame"]), t[0].upper(), t[2:]))
253 region << (simpleStatement | opExpr | notExpr)
254
255 def parse(s):
256 if isinstance(s, pgsphere.PgSAdapter):
257 return s
258 if s is None or not s.strip():
259 return None
260
261 try:
262 res = utils.pyparseString(region, s, parseAll=True)[0]
263 if not res.cooSys or res.cooSys.lower()=='unknownframe':
264 res.cooSys = "UNKNOWN"
265 return res
266 except (ParseException, ParseSyntaxException) as msg:
267 raise common.STCSParseError("Invalid STCS (%s)"%str(msg))
268
269 return parse
270
271
272 parseSimpleSTCS = getSimpleSTCSParser()
276 """returns an spoly for an STC-S region specification.
277
278 This is used to let people upload VOTables with REGION items.
279 """
280 if stcsExpr is None:
281 return None
282 return parseSimpleSTCS(stcsExpr).asPoly()
283
284
285 if __name__=="__main__":
286
287
288 from kapteyn import celestial
289 from gavo.stc import sphermath
290 print("FK4:", tuple(-a for a in
291 sphermath.getEulerAnglesFromMatrix(
292 celestial.skymatrix("icrs", "fk4 B1950.0")[0])))
293 print("GAL:", tuple(-a for a in
294 sphermath.getEulerAnglesFromMatrix(
295 celestial.skymatrix("icrs", "galactic")[0])))
296