1
2
3
4
5
6
7
8
9
10
11 package org.astrogrid.datacenter.queriers.sql;
12
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.io.StringWriter;
17 import java.util.StringTokenizer;
18 import javax.xml.parsers.ParserConfigurationException;
19 import javax.xml.transform.Transformer;
20 import javax.xml.transform.TransformerConfigurationException;
21 import javax.xml.transform.TransformerException;
22 import javax.xml.transform.TransformerFactory;
23 import javax.xml.transform.dom.DOMSource;
24 import javax.xml.transform.stream.StreamResult;
25 import javax.xml.transform.stream.StreamSource;
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.astrogrid.config.ConfigException;
29 import org.astrogrid.config.SimpleConfig;
30 import org.astrogrid.datacenter.query.Adql074Writer;
31 import org.astrogrid.datacenter.query.Query;
32 import org.astrogrid.datacenter.query.QueryException;
33 import org.astrogrid.datacenter.query.condition.Function;
34 import org.astrogrid.datacenter.sky.Angle;
35 import org.astrogrid.util.DomHelper;
36 import org.w3c.dom.Document;
37 import org.w3c.dom.Element;
38 import org.xml.sax.SAXException;
39
40 /***
41 * A 'standard' translator that creates 'standard' SQL
42 */
43 public class StdSqlMaker extends AdqlSqlMaker {
44
45
46 private static final Log log = LogFactory.getLog(StdSqlMaker.class);
47
48 /*** Returns the SQL condition for a circle based on the columns in the
49 * configuration file
50 */
51 public String makeSqlCircleCondition(Angle ra, Angle dec, Angle radius) {
52 String table = SimpleConfig.getSingleton().getString(CONE_SEARCH_TABLE_KEY);
53
54
55 String raCol = table+"."+SimpleConfig.getSingleton().getString(CONE_SEARCH_RA_COL_KEY);
56 String decCol = table+"."+SimpleConfig.getSingleton().getString(CONE_SEARCH_DEC_COL_KEY);
57
58 return makeSqlCircleCondition(raCol, decCol, ra, dec, radius);
59 }
60
61 /*** Returns the SQL condition expression for a circle. This circle is
62 * 'flat' on the sphere, when vieweing the sphere from the center. ie, the
63 * radius is a declination angle from the given RA & DEC point. This means
64 * the circle is distorted in coordinate space
65 */
66 public String makeSqlCircleCondition(String raCol, String decCol, Angle ra, Angle dec, Angle radius) {
67
68
69 boolean funcsInRads = SimpleConfig.getSingleton().getBoolean(DB_TRIGFUNCS_IN_RADIANS);
70
71 String raColRad = makeColumnRadians(raCol);
72 String decColRad = makeColumnRadians(decCol);
73
74
75
76
77
78
79
80 if (funcsInRads) {
81 return
82 makeSqlBoundsCondition(raCol, decCol, ra, dec, radius) + " AND "+
83 "("+
84 "(2 * ASIN( SQRT( "+
85
86 "POWER( SIN( ("+decColRad+" - ("+dec.asRadians()+") ) / 2 ) ,2) + "+
87 "COS("+dec.asRadians()+") * COS("+decColRad+") * "+
88 "POWER( SIN( ("+raColRad+" - "+ra.asRadians()+") / 2 ), 2) "+
89 "))) < "+radius.asRadians()+
90 ")";
91 }
92 else {
93 throw new UnsupportedOperationException("Not done degree funcs yet - do they exist?");
94 }
95 /***/
96 }
97
98 /*** Returns a SQL condition expression to 'bound' a given circle. Otherwise circle queries
99 * query is likely to take a very very long time as it trawls through all the rows calculating
100 * the distance.
101 * <p> At the moment this is just a DEC-binding (which is easier than RA :-)
102 * @param raCol, decCol - the column names that contain the RA & DEC values of the objects
103 */
104 public String makeSqlBoundsCondition(String raCol, String decCol, Angle ra, Angle dec, Angle radius) {
105
106 String colUnits = SimpleConfig.getSingleton().getString(CONE_SEARCH_COL_UNITS_KEY);
107
108 if (dec.asDegrees() - radius.asDegrees() < -90) {
109
110 return "("+decCol+"<"+getAngleInColUnits(Angle.fromRadians(dec.asRadians()+radius.asRadians()))+")";
111 }
112
113 if (dec.asDegrees() + radius.asDegrees() > +90) {
114
115 return "("+decCol+">"+getAngleInColUnits(Angle.fromRadians(dec.asRadians()-radius.asRadians()))+")";
116 }
117
118
119 return "( ("+decCol+"<"+getAngleInColUnits(Angle.fromRadians(dec.asRadians()+radius.asRadians()))+") "+
120 " and "+
121 " ("+decCol+">"+getAngleInColUnits(Angle.fromRadians(dec.asRadians()-radius.asRadians()))+") )";
122
123 }
124
125
126 /***
127 * Returns the right SQL to translate a conesearch column to radians
128 */
129 public String makeColumnRadians(String colName) {
130 String colUnits = SimpleConfig.getSingleton().getString(CONE_SEARCH_COL_UNITS_KEY).trim().toLowerCase();
131
132 if (colUnits.equals("rad")) {
133 return colName;
134 }
135 else if (colUnits.equals("deg")) {
136 return "RADIANS("+colName+")";
137 }
138 else if (colUnits.equals("marcsec")) {
139 return "RADIANS("+colName+"*360000)";
140 }
141 else {
142 throw new ConfigException("Unknown units '"+colUnits+"' for conesearch columns, only 'rad', 'deg' or 'marcsec' supported");
143 }
144 }
145
146 /*** Returns the given angle in the column's units. Useful for making SQL a bit
147 * simpler - convert the angles to the column units rather than vice versa with functions */
148 public String getAngleInColUnits(Angle givenAngle) {
149 String colUnits = SimpleConfig.getSingleton().getString(CONE_SEARCH_COL_UNITS_KEY).trim().toLowerCase();
150 if (colUnits.equals("rad")) {
151 return ""+givenAngle.asRadians();
152 }
153 else if (colUnits.equals("deg")) {
154 return ""+givenAngle.asDegrees();
155 }
156 else if (colUnits.equals("marcsec")) {
157 return ""+(givenAngle.asArcSecs()*1000);
158 }
159 else {
160 throw new ConfigException("Unknown units '"+colUnits+"' for conesearch columns, only 'rad', 'deg' or 'marcsec' supported");
161 }
162 }
163
164
165 /***
166 * Constructs an SQL statement for the given ADQL document by getting the
167 * (super) ADQL/sql and replacing the region
168 */
169 public String makeSql(Query query) throws QueryException {
170 String sql = super.makeSql(query);
171
172 sql = replaceRegion(sql);
173 return sql;
174 }
175
176
177 /***
178 * Replaces the REGION() function with the cone search SQL.
179 * This is a bit messy as we're reparsing the SQL to replace the function
180 * but we need access to the configuration for this...
181 */
182 public String replaceRegion(String sql) {
183
184 int start = sql.toLowerCase().indexOf("region");
185
186 if (start==-1) {
187 return sql;
188 }
189
190 int end = sql.indexOf(")", start);
191
192 int argStart=sql.indexOf("'", start);
193 int argEnd=sql.indexOf("'", argStart+1);
194
195 String regionArg = sql.substring(argStart+1, end-1).trim().toLowerCase();
196
197 StringTokenizer s = new StringTokenizer(regionArg, " ");
198 String shape = s.nextToken();
199
200 if (shape.equals("circle")) {
201
202 String type = s.nextToken();
203 if (type.equals("j2000")) {
204 Angle ra = Angle.fromDegrees(Double.parseDouble(s.nextToken()));
205 Angle dec = Angle.fromDegrees(Double.parseDouble(s.nextToken()));
206 Angle radius = Angle.fromDegrees(Double.parseDouble(s.nextToken()));
207
208 return sql.substring(0,start)+
209 makeSqlCircleCondition(ra, dec, radius)+
210 sql.substring(end+1);
211
212 }
213 else if (type.equals("cartesian")) {
214 throw new UnsupportedOperationException("Can't yet do cartesian circles");
215 }
216 else {
217 throw new QueryException("Unknown circle type: "+type);
218 }
219
220 }
221 else {
222 throw new QueryException("Unknown region shape: "+regionArg);
223 }
224 }
225 }
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354