00001 """
00002 casapy functions for converting ASCII ephemerides from JPL-Horizons into
00003 CASA tables and installing them where casapy can find them.
00004
00005 jplfiles_to_repository() puts it all together, so it is most likely the
00006 function you want.
00007
00008 There are various utilities like convert_radec, datestr*, get_num_from_str,
00009 mean_radius*, and construct_tablepath defined in here as well.
00010 """
00011
00012 from glob import glob
00013 import os
00014 import re
00015 import scipy.special
00016 import time
00017
00018 from taskinit import gentools, qa, casalog
00019 me = gentools(['me'])[0]
00020
00021 from dict_to_table import dict_to_table
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 cols = {
00034 'MJD': {'header': r'Date__\(UT\)__HR:MN',
00035 'comment': 'date',
00036 'pat': r'(?P<MJD>\d+-\w+-\d+ \d+:\d+)'},
00037 'RA': {'header': r'R.A._+\([^)]+',
00038 'comment': 'Right Ascension',
00039 'pat': r'(?P<RA>(\d+ \d+ )?\d+\.\d+)'},
00040 'DEC': {'header': r'\)_+DEC.',
00041 'comment': 'Declination',
00042 'pat': r'(?P<DEC>([-+]?\d+ \d+ )?[-+]?\d+\.\d+)'},
00043 'NP_ang': {'header': r'NP\.ang',
00044 'comment': 'North-Pole pos. angle',
00045 'pat': r'(?P<NP_ang>[0-9.]+|n\.a\.)',
00046 'unit': 'deg'},
00047 'NP_dist': {'header': r'NP\.dist',
00048 'comment': 'North-Pole ang. distance',
00049 'pat': r'(?P<NP_dist>[-+0-9.]+|n\.a\.)',
00050 'unit': 'arcsec'},
00051 'illu': {'header': r'Illu%',
00052
00053 'comment': 'Illuminated fraction',
00054 'pat': r'(?P<illu>[0-9.]+)',
00055 'unit': r'%'},
00056
00057 'DiskLong': {'header': r'Ob-lon',
00058
00059 'comment': 'Sub-observer longitude',
00060
00061 'pat': r'(?P<DiskLong>[0-9.]+|n\.a\.)',
00062 'unit': 'deg'},
00063 'DiskLat': {'header': r'Ob-lat',
00064
00065 'comment': 'Sub-observer latitude',
00066
00067 'pat': r'(?P<DiskLat>[-+0-9.]+|n\.a\.)',
00068 'unit': 'deg'},
00069 'Sl_lon': {'header': r'Sl-lon',
00070 'comment': 'Sub-Solar longitude',
00071 'pat': r'(?P<Sl_lon>[0-9.]+|n\.a\.)',
00072 'unit': 'deg'},
00073 'Sl_lat': {'header': r'Sl-lat',
00074 'comment': 'Sub-Solar longitude',
00075 'pat': r'(?P<Sl_lat>[-+0-9.]+|n\.a\.)',
00076 'unit': 'deg'},
00077
00078
00079 'NP_RA': {'header': r'N\.Pole-RA',
00080 'comment': 'North Pole right ascension',
00081 'pat': r'(?P<NP_RA>(\d+ \d+ )?\d+\.\d+)'},
00082 'NP_DEC': {'header': r'N\.Pole-DC',
00083 'comment': 'North Pole declination',
00084 'pat': r'(?P<NP_DEC>([-+]?\d+ \d+ )?[-+]?\d+\.\d+)'},
00085
00086 'r': {'header': 'r',
00087 'comment': 'heliocentric distance',
00088 'unit': 'AU',
00089 'pat': r'(?P<r>[0-9.]+)'},
00090 'rdot': {'header': 'rdot',
00091
00092 'comment': 'heliocentric distance rate',
00093 'unit': 'km/s',
00094 'pat': r'(?P<rdot>[-+0-9.]+)'},
00095
00096 'Rho': {'header': 'delta',
00097 'comment': 'geocentric distance',
00098 'unit': 'AU',
00099 'pat': r'(?P<Rho>[0-9.]+)'},
00100 'RadVel': {'header': 'deldot',
00101
00102 'comment': 'geocentric distance rate',
00103 'pat': r'(?P<RadVel>[-+0-9.]+)',
00104 'unit': 'km/s'},
00105 'phang': {'header': 'S-T-O',
00106 'comment': 'phase angle',
00107 'unit': 'deg',
00108 'pat': r'(?P<phang>[0-9.]+)'},
00109 'ang_sep': {'header': 'ang-sep/v',
00110 'comment': 'Angular separation from primary',
00111 'pat': r'(?P<ang_sep>[0-9.]+/.)'},
00112
00113
00114
00115
00116
00117
00118
00119
00120 'L_s': {'header': 'L_s',
00121 'unit': 'deg',
00122 'comment': 'Season angle',
00123 'pat': r'(?P<L_s>[-+0-9.]+)'}
00124 }
00125
00126 def readJPLephem(fmfile):
00127 """
00128 Reads a JPL Horizons text file (see
00129 http://ssd.jpl.nasa.gov/horizons.cgi#top ) for a solar system object and
00130 returns various quantities in a dictionary. The dict will be blank ({}) if
00131 there is a failure.
00132 """
00133 retdict = {}
00134 casalog.origin('readJPLephem')
00135
00136
00137 try:
00138 ephem = open(fmfile)
00139 except IOError:
00140 casalog.post("Could not open ephemeris file " + fmfile,
00141 priority="SEVERE")
00142 return {}
00143
00144
00145
00146
00147
00148
00149
00150
00151 headers = {
00152 'NAME': {'pat': r'^Target body name:\s+\d*\s*(\w+)'},
00153 'ephtype': {'pat': r'\?s_type=1#top>\]\s*:\s+\*(\w+)'},
00154 'obsloc': {'pat': r'^Center-site name:\s+(\w+)'},
00155
00156 'meanrad': {'pat': r'(?:Mean radius \(km\)\s*=|^Target radii\s*:)\s*([0-9.]+)(?:\s*km)?\s*$',
00157 'unit': 'km'},
00158
00159
00160 'radii': {'pat': r'Target radii\s*:\s*([0-9.]+\s*x\s*[0-9.]+\s*x\s*[0-9.]+)\s*km.*Equator, meridian, pole|Target radii\s*:\s*([0-9.]+)\s*km\s*',
00161 'unit': 'km'},
00162 'T_mean': {'pat': r'Mean Temperature \(K\)\s*=\s*([0-9.]+)',
00163 'unit': 'K'},
00164
00165
00166 'rot_per': {'pat': r'(?i)(?<!Inferred )\b(rot(ation(al)?|\.)?\s*per.*=\s*([-0-9.]+\s*[dhr]*|Synchronous))'},
00167 'orb_per': {'pat': r'Orbital period((, days)?\s*=\s*[-0-9.]+\s*[dhr](\s*\(?R\)?)?)'},
00168
00169
00170 'GeoLong': {'pat': r'^Center geodetic\s*: ([-+0-9.]+,\s*[-+0-9.]+,\s*[-+0-9.]+)'},
00171 'dMJD': {'pat': r'^Step-size\s*:\s*(.+)'},
00172
00173
00174 'VS_CREATE': {'pat': r'^Ephemeris / \w+ \w+ (\w+\s+\d+\s+\d+:\d+:\d+\s+\d+)'}
00175 }
00176 for hk in headers:
00177 headers[hk]['pat'] = re.compile(headers[hk]['pat'])
00178
00179
00180
00181
00182
00183
00184 datapat = r'^\s*'
00185
00186 stoppat = r'\$\$EOE$'
00187
00188
00189 num_cols = 0
00190 in_data = False
00191 comp_mismatches = []
00192 print_datapat = False
00193
00194 invalid=-999.
00195 for line in ephem:
00196 if in_data:
00197 if re.match(stoppat, line):
00198 break
00199 matchobj = re.search(datapat, line)
00200 if matchobj:
00201 gdict = matchobj.groupdict()
00202 for col in gdict:
00203 if gdict[col]=='n.a.':
00204 gdict[col]=invalid
00205 retdict['data'][col]['data'].append(gdict[col])
00206 if len(gdict) < num_cols:
00207 print "Partially mismatching line:"
00208 print line
00209 print "Found:"
00210 print gdict
00211 print_datapat = True
00212 else:
00213 print_datapat = True
00214
00215 comp_mismatches.append(re.sub(r'\s*$', '', line))
00216 elif re.match(r'^\s*' + cols['MJD']['header'] + r'\s+'
00217 + cols['RA']['header'], line):
00218
00219
00220 havecols = []
00221
00222
00223 m=re.match(r'(^\s*)(\S+)(\s+)('+cols['RA']['header']+')', line)
00224 coordref=m.group(4).split('(')[-1]
00225 cols['RA']['comment']+='('+coordref+')'
00226 cols['DEC']['comment']+='('+coordref+')'
00227
00228 myline = re.sub(r'\s*$', '', line)
00229 titleline = myline
00230 remaining_cols = cols.keys()
00231 found_col = True
00232
00233 while myline and remaining_cols and found_col:
00234 found_col = False
00235
00236
00237 for col in remaining_cols:
00238 if re.match(r'^\s*' + cols[col]['header'], myline):
00239
00240 havecols.append(col)
00241 remaining_cols.remove(col)
00242 myline = re.sub(r'^\s*' + cols[col]['header'],
00243 '', myline)
00244 found_col = True
00245 break
00246 datapat += r'\s+'.join([cols[col]['pat'] for col in havecols])
00247 sdatapat = datapat
00248 casalog.post("Found columns: " + ', '.join(havecols))
00249 datapat = re.compile(datapat)
00250 retdict['data'] = {}
00251 for col in havecols:
00252 if not cols[col].get('unwanted'):
00253 retdict['data'][col] = {'comment': cols[col]['comment'],
00254 'data': []}
00255 num_cols = len(retdict['data'])
00256 elif re.match(r'^\$\$SOE\s*$', line):
00257 casalog.post("Starting to read data.", priority='INFO2')
00258 in_data = True
00259 else:
00260
00261
00262 for hk in headers:
00263 if not retdict.has_key(hk):
00264 matchobj = re.search(headers[hk]['pat'], line)
00265 if matchobj:
00266 if hk=='radii':
00267 mobjs=matchobj.groups()
00268 for gp in mobjs:
00269 if gp!=None:
00270 retdict[hk] = gp
00271 break
00272 break
00273 else:
00274 retdict[hk] = matchobj.group(1)
00275 break
00276 ephem.close()
00277
00278
00279 if comp_mismatches:
00280 print "Completely mismatching lines:"
00281 print "\n".join(comp_mismatches)
00282 if print_datapat:
00283 print "The apparent title line is:"
00284 print titleline
00285 print "datapat = r'%s'" % sdatapat
00286
00287
00288 try:
00289 retdict['earliest'] = datestr_to_epoch(retdict['data']['MJD']['data'][0])
00290 retdict['latest'] = datestr_to_epoch(retdict['data']['MJD']['data'][-1])
00291 except Exception, e:
00292 print "Error!"
00293 if retdict.has_key('data'):
00294 if retdict['data'].has_key('MJD'):
00295 if retdict['data']['MJD'].has_key('data'):
00296
00297 print "retdict['data'] =", retdict['data']
00298 else:
00299 print "retdict['data']['MJD'] has no 'data' key."
00300 print "retdict['data']['MJD'].keys() =", retdict['data']['MJD'].keys()
00301 else:
00302 print "retdict['data'] has no 'MJD' key."
00303 print "retdict['data'].keys() =", retdict['data'].keys()
00304 else:
00305 print "retdict has no 'data' key."
00306 raise e
00307
00308 for hk in headers:
00309 if retdict.has_key(hk):
00310 if headers[hk].has_key('unit'):
00311 if hk == 'radii':
00312 radii = retdict[hk].split('x')
00313 if len(radii)==1:
00314 a = float(radii[0])
00315 retdict[hk] = {'unit': headers[hk]['unit'], 'value': (a,a,a)}
00316 retdict['meanrad'] = {'unit': headers[hk]['unit'],
00317 'value': a}
00318 else:
00319 a, b, c = [float(r) for r in radii]
00320 retdict[hk] = {'unit': headers[hk]['unit'],
00321 'value': (a, b, c)}
00322 retdict['meanrad'] = {'unit': headers[hk]['unit'],
00323 'value': mean_radius(a, b, c)}
00324 else:
00325 try:
00326
00327 if type(retdict[hk]) != dict:
00328 retdict[hk] = {'unit': headers[hk]['unit'],
00329 'value': float(retdict[hk])}
00330 except Exception, e:
00331 print "Error converting header", hk, "to a Quantity."
00332 print "retdict[hk] =", retdict[hk]
00333 raise e
00334 elif hk == 'GeoLong':
00335 long_lat_alt = retdict[hk].split(',')
00336 retdict['GeoLong'] = float(long_lat_alt[0])
00337 retdict['GeoLat'] = float(long_lat_alt[1])
00338 retdict['GeoDist'] = float(long_lat_alt[2])
00339 elif hk == 'dMJD':
00340 retdict[hk] = qa.convert(qa.totime(retdict[hk].replace('minutes', 'min')),
00341 'd')['value']
00342 elif hk == 'orb_per':
00343 unit = 'h'
00344 retrograde = False
00345 if 'd' in retdict[hk].lower():
00346 unit = 'd'
00347 if 'r' in retdict[hk].lower():
00348 retrograde = True
00349 value = get_num_from_str(retdict[hk], 'orbital period')
00350 if value != False:
00351 if retrograde and value > 0.0:
00352 value = -value
00353 retdict[hk] = {'unit': unit, 'value': value}
00354 else:
00355 del retdict[hk]
00356
00357
00358
00359 if 'rot_per' in retdict:
00360 rpstr = retdict['rot_per']
00361 if 'ROTPER' in rpstr:
00362 retdict['rot_per'] = {'unit': 'h',
00363 'value': get_num_from_str(rpstr, 'rotation period')}
00364 elif 'Synchronous' in rpstr:
00365 retdict['rot_per'] = retdict['orb_per']
00366 else:
00367 match = re.search(r'(\d+)h\s*(\d+)m\s*([0-9.]+)s', rpstr)
00368 if match:
00369 hms = [float(match.group(i)) for i in range(1, 4)]
00370 retdict['rot_per'] = {'unit': 'h',
00371 'value': hms[0] + (hms[1] + hms[2] / 60.0) / 60.0}
00372 else:
00373
00374 try:
00375 match = re.search(r'([-0-9.]+)(?:\s*\+-[0-9.]+)?\s*([dh])', rpstr)
00376 if match:
00377 retdict['rot_per'] = {'unit': match.group(2),
00378 'value': float(match.group(1))}
00379 except:
00380 print "Error parsing the rotation period from"
00381 print rpstr
00382
00383 if retdict['data'].has_key('ang_sep'):
00384 retdict['data']['obs_code'] = {'comment': 'Obscuration code'}
00385 for dk in retdict['data']:
00386 if dk == 'obs_code':
00387 continue
00388 if cols[dk].has_key('unit'):
00389 retdict['data'][dk]['data'] = {'unit': cols[dk]['unit'],
00390 'value': scipy.array([float(s) for s in retdict['data'][dk]['data']])}
00391 if dk == 'RadVel':
00392
00393 retdict['data'][dk]['data']['unit'] = 'AU/d'
00394 kmps_to_AUpd = qa.convert('1km/s', 'AU/d')['value']
00395 retdict['data'][dk]['data']['value'] *= kmps_to_AUpd
00396
00397 if re.match(r'.*(RA|DEC)$', dk):
00398 retdict['data'][dk] = convert_radec(retdict['data'][dk])
00399 elif dk == 'MJD':
00400 retdict['data']['MJD'] = datestrs_to_MJDs(retdict['data']['MJD'])
00401 elif dk == 'ang_sep':
00402 angseps = []
00403 obscodes = []
00404 for asoc in retdict['data'][dk]['data']:
00405 angsep, obscode = asoc.split('/')
00406 angseps.append(float(angsep))
00407 obscodes.append(obscode)
00408 retdict['data'][dk]['data'] = {'unit': 'arcseconds',
00409 'value': angseps}
00410 retdict['data']['obs_code']['data'] = obscodes
00411
00412 if len(retdict.get('radii', {'value': []})['value']) == 3 \
00413 and retdict['data'].has_key('NP_RA') and retdict['data'].has_key('NP_DEC'):
00414
00415 retdict['meanrad']['value'] = mean_radius_with_known_theta(retdict)
00416
00417
00418 retdict['VS_TYPE'] = 'Table of comet/planetary positions'
00419 retdict['VS_VERSION'] = '0003.0001'
00420 if retdict.has_key('VS_CREATE'):
00421 dt = time.strptime(retdict['VS_CREATE'], "%b %d %H:%M:%S %Y")
00422 else:
00423 casalog.post("The ephemeris creation date was not found. Using the current time.",
00424 priority="WARN")
00425 dt = time.gmtime()
00426 retdict['VS_CREATE'] = time.strftime('%Y/%m/%d/%H:%M', dt)
00427
00428
00429 retdict['VS_DATE'] = time.strftime('%Y/%m/%d/%H:%M', time.gmtime())
00430
00431 if retdict['data'].has_key('MJD'):
00432 retdict['MJD0'] = retdict['data']['MJD']['value'][0] - retdict['dMJD']
00433 else:
00434 print "The table will not be usable with me.framecomet because it lacks MJD."
00435
00436 return retdict
00437
00438 def convert_radec(radec_col):
00439 """
00440 Returns a column of RAs or declinations as strings, radec_col, as a
00441 quantity column. (Unfortunately MeasComet assumes the columns are
00442 Quantities instead of Measures, and uses GeoDist == 0.0 to toggle between
00443 APP and TOPO.)
00444 """
00445 angstrlist = radec_col['data']
00446 angq = {}
00447 nrows = len(angstrlist)
00448
00449 if len(angstrlist[0].split()) > 1:
00450
00451 if radec_col['comment'][:len("declination")].lower() == 'declination':
00452 for i in xrange(nrows):
00453 dms = angstrlist[i].replace(' ', 'd', 1)
00454 angstrlist[i] = dms.replace(' ', 'm') + 's'
00455 else:
00456 for i in xrange(nrows):
00457 angstrlist[i] = angstrlist[i].replace(' ', ':')
00458
00459
00460 try:
00461 firstang = qa.toangle(angstrlist[0])
00462 except Exception, e:
00463 print "Error: Could not convert", angstrlist[0], "to an angle."
00464 raise e
00465 angq['unit'] = firstang['unit']
00466 angq['value'] = [firstang['value']]
00467
00468 for angstr in angstrlist[1:]:
00469 angq['value'].append(qa.toangle(angstr)['value'])
00470 else:
00471 angq['unit'] = 'deg'
00472 angq['value'] = [float(a) for a in angstrlist]
00473
00474 return {'comment': radec_col['comment'],
00475 'data': {'unit': angq['unit'],
00476 'value': scipy.array(angq['value'])}}
00477
00478 def get_num_from_str(fstr, wanted="float"):
00479 """
00480 Like float(fstr) on steroids, in that it ignores things in fstr that aren't
00481 numbers. Returns False on failure.
00482
00483 wanted: an optional label for the type of number you wanted.
00484 Only used for distinguishing error messages.
00485
00486 Example:
00487 >>> from JPLephem_reader import get_num_from_str
00488 >>> get_num_from_str(' Sidereal rot. period = 58.6462 d ')
00489 58.6462
00490 >>> get_num_from_str('Rotation period = 16.11+-0.01 hr', wanted='rotation period')
00491 16.109999999999999
00492 >>> get_num_from_str('Rotation period = Synchronous', wanted='rotation period')
00493 Could not convert "Rotation period = Synchronous" to a rotation period.
00494 False
00495 """
00496 match = re.search(r'([-+]?(\d+(\.\d*)?|\d*\.\d+)([eEdD][-+]?\d+)?)', fstr)
00497 if match:
00498 value = float(match.group(1))
00499 else:
00500 print "Could not convert \"%s\" to a %s." % (fstr, wanted)
00501 value = False
00502 return value
00503
00504 def mean_radius(a, b, c):
00505 """
00506 Return the average apparent mean radius of an ellipsoid with semiaxes
00507 a >= b >= c.
00508 "average" means average over time naively assuming the pole orientation
00509 is uniformly distributed over the whole sphere, and "apparent mean radius"
00510 means a radius that would give the same area as the apparent disk.
00511 """
00512
00513
00514
00515
00516
00517 R = 0.5 * c**2 * (1.0 / b**2 + 1.0 / a**2)
00518 if R < 0.95:
00519 sqrt1mR = scipy.sqrt(1.0 - R)
00520
00521
00522 try:
00523 Rterm = 0.5 * R * scipy.log((1.0 + sqrt1mR) / (1.0 - sqrt1mR)) / sqrt1mR
00524 except:
00525 Rterm = 0.0
00526 else:
00527
00528
00529 Rterm = 1.0
00530 onemR = 1.0 - R
00531 onemRtothei = 1.0
00532 for i in xrange(1, 5):
00533 onemRtothei *= onemR
00534 Rterm -= onemRtothei / (0.5 + 2.0 * i**2)
00535 avalfabeta = 0.5 * a * b * (1.0 + Rterm)
00536 return scipy.sqrt(avalfabeta)
00537
00538 def mean_radius_with_known_theta(retdict):
00539 """
00540 Return the average apparent mean radius of an ellipsoid with semiaxes
00541 a >= b >= c (= retdict['radii']['value']).
00542 "average" means average over a rotation period, and "apparent mean radius"
00543 means the radius of a circle with the same area as the apparent disk.
00544 """
00545 a = retdict['radii']['value'][0]
00546 b2 = retdict['radii']['value'][1]**2
00547 c2 = retdict['radii']['value'][2]**2
00548 onemboa2 = 1.0 - b2 / a**2
00549 units = {}
00550 values = {}
00551 for c in ['RA', 'DEC', 'NP_RA', 'NP_DEC']:
00552 units[c] = retdict['data'][c]['data']['unit']
00553 values[c] = retdict['data'][c]['data']['value']
00554 av = 0.0
00555 nrows = len(retdict['data']['RA']['data']['value'])
00556 for i in xrange(nrows):
00557 radec = me.direction('app', {'unit': units['RA'], 'value': values['RA'][i]},
00558 {'unit': units['DEC'], 'value': values['DEC'][i]})
00559 np = me.direction('j2000', {'unit': units['NP_RA'], 'value': values['NP_RA'][i]},
00560 {'unit': units['NP_DEC'], 'value': values['NP_DEC'][i]})
00561 szeta2 = scipy.sin(qa.convert(me.separation(radec, np), 'rad')['value'])**2
00562 csinz2 = c2 * szeta2
00563 bcosz2 = b2 * (1.0 - szeta2)
00564 bcz2pcsz2 = bcosz2 + csinz2
00565 m = csinz2 * onemboa2 / bcz2pcsz2
00566 av += (scipy.sqrt(bcz2pcsz2) * scipy.special.ellipe(m) - av) / (i + 1.0)
00567 return scipy.sqrt(2.0 * a * av / scipy.pi)
00568
00569 def datestr_to_epoch(datestr):
00570 """
00571 Given a UT date like "2010-May-01 00:00", returns an epoch measure.
00572 """
00573 return me.epoch(rf='UTC', v0=qa.totime(datestr))
00574
00575 def datestrs_to_MJDs(cdsdict):
00576 """
00577 All of the date strings must have the same reference frame (i.e. UT).
00578 """
00579 datestrlist = cdsdict['data']
00580
00581
00582 datestrlist = [d.replace(' ', 'T') for d in datestrlist]
00583
00584 timeq = {}
00585
00586 firsttime = qa.totime(datestrlist[0])
00587 timeq['unit'] = firsttime['unit']
00588 timeq['value'] = [firsttime['value']]
00589
00590 for datestr in datestrlist[1:]:
00591 timeq['value'].append(qa.totime(datestr)['value'])
00592
00593 return {'unit': timeq['unit'],
00594 'value': scipy.array(timeq['value'])}
00595
00596 def construct_tablepath(fmdict, prefix=''):
00597 """
00598 Construct a suitable pathname for a CASA table made from fmdict,
00599 starting with prefix. prefix can contain a /.
00600
00601 If prefix is not given, it will be set to
00602 "ephem_JPL-Horizons_%s" % fmdict['NAME']
00603 """
00604 if not prefix:
00605 prefix = "ephem_JPL-Horizons_%s" % fmdict['NAME']
00606 return prefix + "_%.0f-%.0f%s%s.tab" % (fmdict['earliest']['m0']['value'],
00607 fmdict['latest']['m0']['value'],
00608 fmdict['latest']['m0']['unit'],
00609 fmdict['latest']['refer'])
00610
00611 def ephem_dict_to_table(fmdict, tablepath='', prefix=''):
00612 """
00613 Converts a dictionary from readJPLephem() to a CASA table, and attempts to
00614 save it to either to tablepath or a constructed directory name.
00615 Returns whether or not it was successful.
00616
00617 If tablepath is blank and prefix is not given, the table will go to
00618 something like ephem_JPL-Horizons_NAME_EARLIEST-LATESTdUTC.tab.
00619
00620 If tablepath is blank and prefix is given, the table will go to
00621 something like prefix_EARLIEST-LATESTdUTC.tab. prefix can contain a /.
00622 """
00623 if not tablepath:
00624 tablepath = construct_tablepath(fmdict, prefix)
00625 print "Writing to", tablepath
00626
00627
00628 if tablepath=='.' or tablepath=='./' or tablepath.isspace():
00629 raise Exception, "Invalid tablepath: "+tablepath
00630 retval = True
00631
00632 keepcolorder=False
00633 try:
00634 outdict = fmdict.copy()
00635 kws = fmdict.keys()
00636 kws.remove('data')
00637 collist = outdict['data'].keys()
00638
00639
00640
00641 collist.sort()
00642
00643
00644
00645
00646
00647 put_these_first = ['MJD', 'RA', 'DEC', 'Rho', 'RadVel', 'NP_ang', 'NP_dist',
00648
00649 'DiskLong', 'DiskLat', 'Sl_lon', 'Sl_lat', 'r','rdot']
00650
00651 put_these_first.reverse()
00652 for c in put_these_first:
00653 if c in collist:
00654 collist.remove(c)
00655 collist.insert(0, c)
00656
00657 clashing_cols = [c for c in collist if c in kws]
00658 if clashing_cols:
00659 raise ValueError, 'The input dictionary lists ' + ', '.join(clashing_cols) + ' as both keyword(s) and column(s)'
00660
00661
00662
00663 outdict.update(outdict.pop('data'))
00664
00665
00666
00667
00668 info = {'readme': 'Derived by JPLephem_reader.py from a JPL-Horizons ephemeris (http://ssd.jpl.nasa.gov/horizons.cgi#top)',
00669 'subType': 'Comet', 'type': 'IERS'}
00670
00671 retval = dict_to_table(outdict, tablepath, kws, collist, info, keepcolorder)
00672 except Exception, e:
00673 print 'Error', e, 'trying to export an ephemeris dict to', tablepath
00674 retval = False
00675
00676 return retval
00677
00678
00679 def jplfiles_to_repository(objs, jpldir='.', jplext='.ephem',
00680 log='null'):
00681 """
00682 For each Solar System object obj in the list objs,
00683 look for matching JPL-Horizons ASCII files with jplext in jpldir,
00684 read them into python dictionaries,
00685 write the dicts to CASA tables in $CASAROOT/data/ephemerides/JPL-Horizons/,
00686 and check that they can be read by me.framecomet().
00687 Returns the number of ephemerides processed + readable by me.framecomet.
00688
00689 jpldir and jplext can be glob patterns.
00690
00691 $CASAROOT is derived from $CASAPATH.
00692
00693 Log messages will be directed to log for the duration of this function.
00694 Note that 'null' makes a NullLogSink, so it might be better than /dev/null.
00695
00696 Example:
00697 import recipes.ephemerides.request as jplreq
00698 objs = jplreq.asteroids.keys() + jplreq.planets_and_moons.keys()
00699 jplfiles_to_repository(objs, os.getenv('CASAPATH').split()[0])
00700 """
00701 neph = 0
00702 casapath = os.getenv('CASAPATH')
00703 if not casapath:
00704 print "CASAPATH is not set."
00705 return 0
00706 datadir = casapath.split()[0] + '/data/ephemerides/JPL-Horizons'
00707 if not os.path.isdir(datadir):
00708 try:
00709 os.mkdir(datadir)
00710 print "Created", datadir
00711 print "You should probably svn add it."
00712 except Exception, e:
00713 "Error", e, "creating", datadir
00714 return 0
00715 datadir += '/'
00716
00717
00718
00719
00720
00721
00722
00723 if jpldir[-1] != '/':
00724 jpldir += '/'
00725 for sob in objs:
00726 capob = sob.capitalize()
00727 lob = sob.lower()
00728 jplfiles = glob(jpldir + lob + jplext) + glob(jpldir + capob + jplext)
00729 for jplfile in jplfiles:
00730 casalog.post('Reading ' + jplfile)
00731 fmdict = readJPLephem(jplfile)
00732 tabpath = construct_tablepath(fmdict, datadir + capob)
00733 ephem_dict_to_table(fmdict, tabpath)
00734
00735
00736 epoch = fmdict['earliest']
00737 epoch['m0']['value'] += 0.5 * (fmdict['latest']['m0']['value'] -
00738 epoch['m0']['value'])
00739 me.doframe(epoch)
00740 if me.framecomet(tabpath):
00741 neph += 1
00742 else:
00743 casalog.post(tabpath + " was not readable by me.framecomet.",
00744 'WARN')
00745
00746
00747
00748 return neph