2
2
# 1. standard library imports
3
3
import numpy as np
4
4
from collections import OrderedDict
5
+ import re
5
6
6
7
# 2. third party imports
7
8
from astropy .time import Time
8
- from astropy .table import Table , Column
9
+ from astropy import table
10
+ from astropy .io import ascii
11
+ from bs4 import BeautifulSoup
9
12
10
13
# 3. local imports - use relative imports
11
14
# commonly required local imports shown below as example
@@ -27,25 +30,25 @@ class RingNodeClass(BaseQuery):
27
30
28
31
TIMEOUT = conf .timeout
29
32
30
- def __init__ (self , planet = None , obstime = None ):
33
+ def __init__ (self , planet = None , obs_time = None ):
31
34
'''Instantiate Planetary Ring Node query
32
35
33
36
Parameters
34
37
----------
35
38
planet : str, required. one of Jupiter, Saturn, Uranus, or Neptune
36
39
Name, number, or designation of the object to be queried.
37
- obstime : str, in JD or MJD format. If no obstime is provided, the current
40
+ obs_time : str, in JD or MJD format. If no obs_time is provided, the current
38
41
time is used.
39
42
'''
40
43
41
44
super ().__init__ ()
42
45
self .planet = planet
43
46
if self .planet is not None :
44
47
self .planet = self .planet .lower ()
45
- if self .planet not in ['jupiter' , 'saturn' , 'uranus' , 'neptune' ]:
46
- raise ValueError ("illegal value for 'planet' parameter (must be Jupiter, Saturn, Uranus, or Neptune " )
48
+ if self .planet not in ['mars' , ' jupiter' , 'saturn' , 'uranus' , 'neptune' , 'pluto ' ]:
49
+ raise ValueError ("illegal value for 'planet' parameter (must be 'Mars', ' Jupiter', ' Saturn', ' Uranus', 'Neptune', or 'Pluto' " )
47
50
48
- self .obstime = obstime
51
+ self .obs_time = obs_time
49
52
50
53
51
54
def __str__ (self ):
@@ -56,13 +59,13 @@ def __str__(self):
56
59
--------
57
60
>>> from astroquery.solarsystem.pds import RingNode
58
61
>>> uranus = Horizons(planet='Uranus',
59
- ... obstime ='2017-01-01 00:00
62
+ ... obs_time ='2017-01-01 00:00
60
63
>>> print(uranus) # doctest: +SKIP
61
- PDSRingNode instance "Uranus"; obstime =2017-01-01 00:00
64
+ PDSRingNode instance "Uranus"; obs_time =2017-01-01 00:00
62
65
'''
63
- return ('PDSRingNode instance \" {:s}\" ; obstime ={:s}' ).format (
66
+ return ('PDSRingNode instance \" {:s}\" ; obs_time ={:s}' ).format (
64
67
str (self .planet ),
65
- str (self .obstime ))
68
+ str (self .obs_time ))
66
69
# --- pretty stuff above this line, get it working below this line ---
67
70
68
71
def ephemeris_async (self ,
@@ -83,7 +86,7 @@ def ephemeris_async(self,
83
86
--------
84
87
>>> from astroquery.planetary.pds import RingNode
85
88
>>> uranus = Horizons(planet='Uranus',
86
- ... obstime ='2017-01-01 00:00
89
+ ... obs_time ='2017-01-01 00:00
87
90
>>> eph = obj.ephemeris() # doctest: +SKIP
88
91
>>> print(eph) # doctest: +SKIP
89
92
table here...
@@ -95,20 +98,25 @@ def ephemeris_async(self,
95
98
# check for required information
96
99
if self .planet is None :
97
100
raise ValueError ("'planet' parameter not set. Query aborted." )
98
- if self .obstime is None :
99
- self .obstime = Time .now ().jd
101
+ if self .obs_time is None :
102
+ self .obs_time = Time .now ().jd
103
+
104
+ '''
105
+ https://pds-rings.seti.org/cgi-bin/tools/viewer3_xxx.pl?abbrev=jup&ephem=000+JUP365+%2B+DE440&time=2021-10-07+07%3A25&fov=10&fov_unit=Jupiter+radii¢er=body¢er_body=Jupiter¢er_ansa=Main+Ring¢er_ew=east¢er_ra=¢er_ra_type=hours¢er_dec=¢er_star=&viewpoint=observatory&observatory=Earth%27s+center&latitude=&longitude=&lon_dir=east&altitude=&moons=516+All+inner+moons+%28J1-J5%2CJ14-J16%29&rings=Main+%26+Gossamer&torus_inc=6.8&torus_rad=422000&extra_ra=&extra_ra_type=hours&extra_dec=&extra_name=&title=&labels=Small+%286+points%29&moonpts=0&blank=No&meridians=Yes&output=HTML
106
+ '''
100
107
101
108
# configure request_payload for ephemeris query
102
109
# start with successful query and incrementally de-hardcode stuff
110
+ # thankfully, adding extra planet-specific keywords here does not break query for other planets
103
111
request_payload = OrderedDict ([
104
112
('abbrev' , self .planet [:3 ]),
105
113
('ephem' , conf .planet_defaults [self .planet ]['ephem' ]), # change hardcoding for other planets
106
- ('time' , self .obstime ),
114
+ ('time' , self .obs_time ),
107
115
('fov' , 10 ), #for now do not care about the diagram. next few hardcoded
108
116
('fov_unit' , self .planet .capitalize ()+ ' radii' ),
109
117
('center' , 'body' ),
110
118
('center_body' , self .planet .capitalize ()),
111
- ('center_ansa' , 'Epsilon Ring' ),
119
+ ('center_ansa' , conf . planet_defaults [ self . planet ][ 'center_ansa' ] ),
112
120
('center_ew' , 'east' ),
113
121
('center_ra' , '' ),
114
122
('center_ra_type' , 'hours' ),
@@ -121,7 +129,8 @@ def ephemeris_async(self,
121
129
('lon_dir' ,'' ), # de-hardcode later!
122
130
('altitude' ,'' ), # de-hardcode later!
123
131
('moons' ,conf .planet_defaults [self .planet ]['moons' ]), # change hardcoding for other planets
124
- ('rings' ,'All rings' ), # check if this works for other planets
132
+ ('rings' ,conf .planet_defaults [self .planet ]['rings' ]), # check if this works for other planets
133
+ ('arcmodel' ,'#3 (820.1121 deg/day)' ), # check if this works for other planets
125
134
('extra_ra' ,'' ), # diagram stuff. next few can be hardcoded
126
135
('extra_ra_type' ,'hours' ),
127
136
('extra_dec' ,'' ),
@@ -130,8 +139,10 @@ def ephemeris_async(self,
130
139
('labels' ,'Small (6 points)' ),
131
140
('moonpts' ,'0' ),
132
141
('blank' , 'No' ),
142
+ ('opacity' , 'Transparent' ),
133
143
('peris' , 'None' ),
134
144
('peripts' , '4' ),
145
+ ('arcpts' , '4' ),
135
146
('meridians' , 'Yes' ),
136
147
('output' , 'html' )
137
148
])
@@ -160,13 +171,134 @@ def ephemeris_async(self,
160
171
def _parse_ringnode (self , src ):
161
172
'''
162
173
Routine for parsing data from ring node
174
+
175
+ Parameters
176
+ ----------
177
+ self : HorizonsClass instance
178
+ src : list
179
+ raw response from server
180
+
181
+
182
+ Returns
183
+ -------
184
+ data : `astropy.Table`
163
185
'''
164
186
165
187
self .raw_response = src
188
+ soup = BeautifulSoup (src , 'html.parser' )
189
+ text = soup .get_text ()
190
+ #print(repr(text))
191
+ textgroups = re .split ('\n \n |\n \n ' , text )
192
+ ringtable = None
193
+ for group in textgroups :
194
+ group = group .strip (', \n ' )
195
+
196
+ #input parameters. only thing needed is obs_time
197
+ if group .startswith ('Observation' ):
198
+ obs_time = group .split ('\n ' )[0 ].split ('e: ' )[- 1 ].strip (', \n ' )
199
+ self .obs_time = obs_time
200
+
201
+ #minor body table part 1
202
+ elif group .startswith ('Body' ):
203
+ group = 'NAIF ' + group #fixing lack of header for NAIF ID
204
+ bodytable = ascii .read (group , format = 'fixed_width' ,
205
+ col_starts = (0 , 4 , 18 , 35 , 54 , 68 , 80 , 91 ),
206
+ col_ends = (4 , 18 , 35 , 54 , 68 , 80 , 91 , 102 ),
207
+ names = ('NAIF ID' , 'Body' , 'RA' , 'Dec' , 'RA (deg)' , 'Dec (deg)' , 'dRA' , 'dDec' )
208
+ )
209
+ #minor body table part 2
210
+ elif group .startswith ('Sub-' ):
211
+ group = '\n ' .join (group .split ('\n ' )[1 :]) #fixing two-row header
212
+ group = 'NAIF' + group [4 :]
213
+ bodytable2 = ascii .read (group , format = 'fixed_width' ,
214
+ col_starts = (0 , 4 , 18 , 28 , 37 , 49 , 57 , 71 ),
215
+ col_ends = (4 , 18 , 28 , 37 , 49 , 57 , 71 , 90 ),
216
+ names = ('NAIF ID' , 'Body' , 'sub_obs_lon' , 'sub_obs_lat' , 'sub_sun_lon' , 'sub_sun_lat' , 'phase' , 'distance' )
217
+ )
218
+ ## to do: add units!!
219
+
220
+ #ring plane data
221
+ elif group .startswith ('Ring s' ):
222
+ lines = group .split ('\n ' )
223
+ for line in lines :
224
+ l = line .split (':' )
225
+ if 'Ring sub-solar latitude' in l [0 ]:
226
+ [sub_sun_lat , sub_sun_lat_min , sub_sun_lat_max ] = [float (s .strip (', \n ()' )) for s in re .split ('\(|to' , l [1 ])]
227
+ systemtable = table .Table ([['sub_sun_lat' ,'sub_sun_lat_max' ,'sub_sun_lat_min' ],
228
+ [sub_sun_lat , sub_sun_lat_max , sub_sun_lat_min ]],
229
+ names = ('Parameter' , 'Value' ))
230
+
231
+ elif 'Ring plane opening angle' in l [0 ]:
232
+ systemtable .add_row (['opening_angle' , float (re .sub ('[a-zA-Z]+' , '' , l [1 ]).strip (', \n ()' ))])
233
+ elif 'Ring center phase angle' in l [0 ]:
234
+ systemtable .add_row (['phase_angle' , float (l [1 ].strip (', \n ' ))])
235
+ elif 'Sub-solar longitude' in l [0 ]:
236
+ systemtable .add_row (['sub_sun_lon' , float (re .sub ('[a-zA-Z]+' , '' , l [1 ]).strip (', \n ()' ))])
237
+ elif 'Sub-observer longitude' in l [0 ]:
238
+ systemtable .add_row (['sub_obs_lon' , float (l [1 ].strip (', \n ' ))])
239
+ else :
240
+ pass
241
+ ## to do: add units?
242
+
243
+ #basic info about the planet
244
+ elif group .startswith ('Sun-planet' ):
245
+ lines = group .split ('\n ' )
246
+ for line in lines :
247
+ l = line .split (':' )
248
+ if 'Sun-planet distance (AU)' in l [0 ]:
249
+ systemtable .add_row (['d_sun_AU' , float (l [1 ].strip (', \n ' ))])
250
+ elif 'Observer-planet distance (AU)' in l [0 ]:
251
+ systemtable .add_row (['d_obs_AU' , float (l [1 ].strip (', \n ' ))])
252
+ elif 'Sun-planet distance (km)' in l [0 ]:
253
+ systemtable .add_row (['d_sun_km' , float (l [1 ].split ('x' )[0 ].strip (', \n ' ))* 1e6 ])
254
+ elif 'Observer-planet distance (km)' in l [0 ]:
255
+ systemtable .add_row (['d_obs_km' , float (l [1 ].split ('x' )[0 ].strip (', \n ' ))* 1e6 ])
256
+ elif 'Light travel time' in l [0 ]:
257
+ systemtable .add_row (['light_time' , float (l [1 ].strip (', \n ' ))])
258
+ else :
259
+ pass
260
+
261
+ ## to do: add units?
262
+
263
+ # --------- below this line, planet-specific info ------------
264
+ # Uranus individual rings data
265
+ elif group .startswith ('Ring ' ):
266
+ ringtable = ascii .read (' ' + group , format = 'fixed_width' ,
267
+ col_starts = (5 , 18 , 29 ),
268
+ col_ends = (18 , 29 , 36 ),
269
+ names = ('ring' , 'pericenter' , 'ascending node' )
270
+ )
271
+
272
+ # Saturn F-ring data - NEEDS TESTING
273
+ elif group .startswith ('F Ring' ):
274
+ lines = group .split ('\n ' )
275
+ for line in lines :
276
+ l = line .split (':' )
277
+ if 'F Ring pericenter' in l [0 ]:
278
+ peri = float (re .sub ('[a-zA-Z]+' , '' , l [1 ]).strip (', \n ()' ))
279
+ elif 'F Ring ascending node' in l [0 ]:
280
+ ascn = float (l [1 ].strip (', \n ' ))
281
+ ringtable = table .Table ([['F' ], [peri ], [ascn ]], names = ('ring' , 'pericenter' , 'ascending node' ))
282
+
283
+ # Neptune ring arcs data - NEEDS TESTING
284
+ elif group .startswith ('Courage' ):
285
+ lines = group .split ('\n ' )
286
+ for i in range (len (lines )):
287
+ l = lines [i ].split (':' )
288
+ ring = l [0 ].split ('longitude' )[0 ].strip (', \n ' )
289
+ [min_angle , max_angle ] = [float (s .strip (', \n ' )) for s in re .sub ('[a-zA-Z]+' , '' , l [1 ]).strip (', \n ()' ).split ()]
290
+ if i == 0 :
291
+ ringtable = table .Table ([[ring ], [min_angle ], [max_angle ]], names = ('ring' , 'min_angle' , 'max_angle' ))
292
+ else :
293
+ ringtable .add_row ([ring , min_angle , max_angle ])
294
+
295
+ else :
296
+ pass
166
297
298
+ # concatenate minor body table
299
+ bodytable = table .join (bodytable , bodytable2 )
167
300
168
-
169
- return src
301
+ return systemtable , bodytable , ringtable
170
302
171
303
def _parse_result (self , response , verbose = None ):
172
304
'''
@@ -185,7 +317,7 @@ def _parse_result(self, response, verbose = None):
185
317
'''
186
318
self .last_response = response
187
319
try :
188
- data = self ._parse_ringnode (response .text )
320
+ systemtable , bodytable , ringtable = self ._parse_ringnode (response .text )
189
321
except :
190
322
try :
191
323
self ._last_query .remove_cache_file (self .cache_location )
@@ -194,13 +326,30 @@ def _parse_result(self, response, verbose = None):
194
326
# won't be needed
195
327
pass
196
328
raise
197
- return data # astropy table
329
+ return systemtable , bodytable , ringtable #astropy table, astropy table, astropy table
198
330
199
331
RingNode = RingNodeClass ()
200
332
201
333
if __name__ == "__main__" :
202
334
203
- uranus = RingNodeClass ('Uranus' , '2019-10-28 00:00' )
204
- response = uranus .ephemeris ()
205
- print (response )
335
+ # single basic query
336
+ neptune = RingNodeClass ('Neptune' , '2019-10-28 00:00' )
337
+ systemtable , bodytable , ringtable = neptune .ephemeris ()
338
+
339
+ '''
340
+ # basic query for all six targets
341
+ for major_body in ['mars', 'jupiter', 'uranus', 'saturn', 'neptune', 'pluto']:
342
+ nodequery = RingNode(major_body, '2022-05-03 00:00')
343
+ systemtable, bodytable, ringtable = nodequery.ephemeris()
344
+
345
+ print(' ')
346
+ print(' ')
347
+ print('~'*40)
348
+ print(major_body)
349
+ print('~'*40)
350
+ print(systemtable)
351
+ print(bodytable)
352
+ print(ringtable)
353
+ '''
354
+
206
355
0 commit comments