|
| 1 | +""" |
| 2 | +Utilities for generating ADQL for ALMA TAP service |
| 3 | +""" |
| 4 | +from datetime import datetime |
| 5 | + |
| 6 | +from astropy import units as u |
| 7 | +import astropy.coordinates as coord |
| 8 | +from astropy.time import Time |
| 9 | + |
| 10 | +ALMA_DATE_FORMAT = '%d-%m-%Y' |
| 11 | + |
| 12 | + |
| 13 | +def _gen_pos_sql(field, value): |
| 14 | + result = '' |
| 15 | + if field == 'SkyCoord.from_name': |
| 16 | + # resolve the source first |
| 17 | + if value: |
| 18 | + obj_coord = coord.SkyCoord.from_name(value) |
| 19 | + frame = 'icrs' |
| 20 | + ras = [str(obj_coord.icrs.ra.to(u.deg).value)] |
| 21 | + decs = [str(obj_coord.icrs.dec.to(u.deg).value)] |
| 22 | + radius = 10 * u.arcmin |
| 23 | + else: |
| 24 | + raise ValueError('Object name missing') |
| 25 | + else: |
| 26 | + if field == 's_ra, s_dec': |
| 27 | + frame = 'icrs' |
| 28 | + else: |
| 29 | + frame = 'galactic' |
| 30 | + radius = 10*u.arcmin |
| 31 | + if ',' in value: |
| 32 | + center_coord, rad = value.split(',') |
| 33 | + try: |
| 34 | + radius = float(rad.strip())*u.degree |
| 35 | + except ValueError: |
| 36 | + raise ValueError('Cannot parse radius in ' + value) |
| 37 | + else: |
| 38 | + center_coord = value.strip() |
| 39 | + try: |
| 40 | + ra, dec = center_coord.split(' ') |
| 41 | + except ValueError: |
| 42 | + raise ValueError('Cannot find ra/dec in ' + value) |
| 43 | + ras = _val_parse(ra, val_type=str) |
| 44 | + decs = _val_parse(dec, val_type=str) |
| 45 | + |
| 46 | + for ra in ras: |
| 47 | + for dec in decs: |
| 48 | + if result: |
| 49 | + result += ' OR ' |
| 50 | + if isinstance(ra, str) and isinstance(dec, str): |
| 51 | + # circle |
| 52 | + center = coord.SkyCoord(ra, dec, |
| 53 | + unit=(u.deg, u.deg), |
| 54 | + frame=frame) |
| 55 | + |
| 56 | + result += \ |
| 57 | + "CONTAINS(POINT('ICRS',s_ra,s_dec),CIRCLE('ICRS',{},{},{}))=1".\ |
| 58 | + format(center.icrs.ra.to(u.deg).value, |
| 59 | + center.icrs.dec.to(u.deg).value, |
| 60 | + radius.to(u.deg).value) |
| 61 | + else: |
| 62 | + raise ValueError('Cannot interpret ra({}), dec({}'. |
| 63 | + format(ra, dec)) |
| 64 | + if ' OR ' in result: |
| 65 | + # use brackets for multiple ORs |
| 66 | + return '(' + result + ')' |
| 67 | + else: |
| 68 | + return result |
| 69 | + |
| 70 | + |
| 71 | +def _gen_numeric_sql(field, value): |
| 72 | + result = '' |
| 73 | + for interval in _val_parse(value, float): |
| 74 | + if result: |
| 75 | + result += ' OR ' |
| 76 | + if isinstance(interval, tuple): |
| 77 | + int_min, int_max = interval |
| 78 | + if int_min is None: |
| 79 | + if int_max is None: |
| 80 | + # no constraints on bandwith |
| 81 | + pass |
| 82 | + else: |
| 83 | + result += '{}<={}'.format(field, int_max) |
| 84 | + elif int_max is None: |
| 85 | + result += '{}>={}'.format(field, int_min) |
| 86 | + else: |
| 87 | + result += '({1}<={0} AND {0}<={2})'.format(field, int_min, |
| 88 | + int_max) |
| 89 | + else: |
| 90 | + result += '{}={}'.format(field, interval) |
| 91 | + if ' OR ' in result: |
| 92 | + # use brakets for multiple ORs |
| 93 | + return '(' + result + ')' |
| 94 | + else: |
| 95 | + return result |
| 96 | + |
| 97 | + |
| 98 | +def _gen_str_sql(field, value): |
| 99 | + result = '' |
| 100 | + for interval in _val_parse(value, str): |
| 101 | + if result: |
| 102 | + result += ' OR ' |
| 103 | + if '*' in interval: |
| 104 | + # use LIKE |
| 105 | + # escape wildcards if they exists in the value |
| 106 | + interval = interval.replace('%', r'\%') # noqa |
| 107 | + interval = interval.replace('_', r'\_') # noqa |
| 108 | + # ADQL wild cards are % and _ |
| 109 | + interval = interval.replace('*', '%') |
| 110 | + interval = interval.replace('?', '_') |
| 111 | + result += "{} LIKE '{}'".format(field, interval) |
| 112 | + else: |
| 113 | + result += "{}='{}'".format(field, interval) |
| 114 | + if ' OR ' in result: |
| 115 | + # use brackets for multiple ORs |
| 116 | + return '(' + result + ')' |
| 117 | + else: |
| 118 | + return result |
| 119 | + |
| 120 | + |
| 121 | +def _gen_datetime_sql(field, value): |
| 122 | + result = '' |
| 123 | + for interval in _val_parse(value, str): |
| 124 | + if result: |
| 125 | + result += ' OR ' |
| 126 | + if isinstance(interval, tuple): |
| 127 | + min_datetime, max_datetime = interval |
| 128 | + if max_datetime is None: |
| 129 | + result += "{}>={}".format( |
| 130 | + field, Time(datetime.strptime(min_datetime, ALMA_DATE_FORMAT)).mjd) |
| 131 | + elif min_datetime is None: |
| 132 | + result += "{}<={}".format( |
| 133 | + field, Time(datetime.strptime(max_datetime, ALMA_DATE_FORMAT)).mjd) |
| 134 | + else: |
| 135 | + result += "({1}<={0} AND {0}<={2})".format( |
| 136 | + field, Time(datetime.strptime(min_datetime, ALMA_DATE_FORMAT)).mjd, |
| 137 | + Time(datetime.strptime(max_datetime, ALMA_DATE_FORMAT)).mjd) |
| 138 | + else: |
| 139 | + # TODO is it just a value (midnight) or the entire day? |
| 140 | + result += "{}={}".format( |
| 141 | + field, Time(datetime.strptime(interval, ALMA_DATE_FORMAT)).mjd) |
| 142 | + if ' OR ' in result: |
| 143 | + # use brackets for multiple ORs |
| 144 | + return '(' + result + ')' |
| 145 | + else: |
| 146 | + return result |
| 147 | + |
| 148 | + |
| 149 | +def _gen_spec_res_sql(field, value): |
| 150 | + # This needs special treatment because spectral_resolution in AQ is in |
| 151 | + # KHz while corresponding em_resolution is in m |
| 152 | + result = '' |
| 153 | + for interval in _val_parse(value): |
| 154 | + if result: |
| 155 | + result += ' OR ' |
| 156 | + if isinstance(interval, tuple): |
| 157 | + min_val, max_val = interval |
| 158 | + if max_val is None: |
| 159 | + result += "{}<={}".format( |
| 160 | + field, |
| 161 | + min_val*u.kHz.to(u.m, equivalencies=u.spectral())) |
| 162 | + elif min_val is None: |
| 163 | + result += "{}>={}".format( |
| 164 | + field, |
| 165 | + max_val*u.kHz.to(u.m, equivalencies=u.spectral())) |
| 166 | + else: |
| 167 | + result += "({1}<={0} AND {0}<={2})".format( |
| 168 | + field, |
| 169 | + max_val*u.kHz.to(u.m, equivalencies=u.spectral()), |
| 170 | + min_val*u.kHz.to(u.m, equivalencies=u.spectral())) |
| 171 | + else: |
| 172 | + result += "{}={}".format( |
| 173 | + field, interval*u.kHz.to(u.m, equivalencies=u.spectral())) |
| 174 | + if ' OR ' in result: |
| 175 | + # use brackets for multiple ORs |
| 176 | + return '(' + result + ')' |
| 177 | + else: |
| 178 | + return result |
| 179 | + |
| 180 | + |
| 181 | +def _gen_pub_sql(field, value): |
| 182 | + if value is True: |
| 183 | + return "{}='Public'".format(field) |
| 184 | + elif value is False: |
| 185 | + return "{}='Proprietary'".format(field) |
| 186 | + else: |
| 187 | + return None |
| 188 | + |
| 189 | + |
| 190 | +def _gen_science_sql(field, value): |
| 191 | + if value is True: |
| 192 | + return "{}='T'".format(field) |
| 193 | + elif value is False: |
| 194 | + return "{}='F'".format(field) |
| 195 | + else: |
| 196 | + return None |
| 197 | + |
| 198 | + |
| 199 | +def _gen_band_list_sql(field, value): |
| 200 | + # band list value is expected to be space separated list of bands |
| 201 | + if isinstance(value, list): |
| 202 | + val = value |
| 203 | + else: |
| 204 | + val = value.split(' ') |
| 205 | + return _gen_str_sql(field, '|'.join( |
| 206 | + ['*{}*'.format(_) for _ in val])) |
| 207 | + |
| 208 | + |
| 209 | +def _gen_pol_sql(field, value): |
| 210 | + # band list value is expected to be space separated list of bands |
| 211 | + val = '' |
| 212 | + states_map = {'Stokes I': '*I*', |
| 213 | + 'Single': '/LL/', |
| 214 | + 'Dual': '/LL/RR/', |
| 215 | + 'Full': '/LL/LR/RL/RR/'} |
| 216 | + for state in states_map: |
| 217 | + if state in value: |
| 218 | + if val: |
| 219 | + val += '|' |
| 220 | + val += states_map[state] |
| 221 | + return _gen_str_sql(field, val) |
| 222 | + |
| 223 | + |
| 224 | +def _val_parse(value, val_type=float): |
| 225 | + # parses an ALMA query field and returns a list of values (of type |
| 226 | + # val_type) or tuples representing parsed values or intervals. Open |
| 227 | + # intervals have None at one of the ends |
| 228 | + def _one_val_parse(value, val_type=float): |
| 229 | + # parses the value and returns corresponding interval for |
| 230 | + # sia to work with. E.g <2 => (None, 2) |
| 231 | + if value.startswith('<'): |
| 232 | + return (None, val_type(value[1:])) |
| 233 | + elif value.startswith('>'): |
| 234 | + return (val_type(value[1:]), None) |
| 235 | + else: |
| 236 | + return val_type(value) |
| 237 | + result = [] |
| 238 | + if isinstance(value, str): |
| 239 | + try: |
| 240 | + if value.startswith('!'): |
| 241 | + start, end = _val_parse(value[2:-1].strip(), val_type=val_type)[0] |
| 242 | + result.append((None, start)) |
| 243 | + result.append((end, None)) |
| 244 | + elif value.startswith('('): |
| 245 | + result += _val_parse(value[1:-1], val_type=val_type) |
| 246 | + elif '|' in value: |
| 247 | + for vv in value.split('|'): |
| 248 | + result += _val_parse(vv.strip(), val_type=val_type) |
| 249 | + elif '..' in value: |
| 250 | + start, end = value.split('..') |
| 251 | + if not start or not end: |
| 252 | + raise ValueError('start or end interval missing in {}'. |
| 253 | + format(value)) |
| 254 | + result.append((_one_val_parse(start.strip(), val_type=val_type), |
| 255 | + _one_val_parse(end.strip(), val_type=val_type))) |
| 256 | + else: |
| 257 | + result.append(_one_val_parse(value, val_type=val_type)) |
| 258 | + except Exception as e: |
| 259 | + raise ValueError( |
| 260 | + 'Error parsing {}. Details: {}'.format(value, str(e))) |
| 261 | + elif isinstance(value, list): |
| 262 | + result = value |
| 263 | + else: |
| 264 | + result.append(value) |
| 265 | + return result |
0 commit comments