8
8
from astropy .time import Time
9
9
from astropy import table
10
10
from astropy .io import ascii
11
+ import astropy .units as u
12
+ from astropy .coordinates import EarthLocation , Angle
11
13
from bs4 import BeautifulSoup
12
14
13
15
# 3. local imports - use relative imports
@@ -26,22 +28,6 @@ class RingNodeClass(BaseQuery):
26
28
for querying the Planetary Ring Node ephemeris tools
27
29
<https://pds-rings.seti.org/tools/>
28
30
29
-
30
- # basic query for all six targets
31
- for major_body in ['mars', 'jupiter', 'uranus', 'saturn', 'neptune', 'pluto']:
32
- nodequery = RingNode(major_body, '2022-05-03 00:00')
33
- systemtable, bodytable, ringtable = nodequery.ephemeris()
34
-
35
- print(' ')
36
- print(' ')
37
- print('~'*40)
38
- print(major_body)
39
- print('~'*40)
40
- print(systemtable)
41
- print(bodytable)
42
- print(ringtable)
43
-
44
-
45
31
"""
46
32
47
33
TIMEOUT = conf .timeout
@@ -51,37 +37,31 @@ def __init__(self, planet=None, obs_time=None):
51
37
52
38
Parameters
53
39
----------
54
- planet : str, required. one of Jupiter, Saturn, Uranus, or Neptune
55
- Name, number, or designation of the object to be queried.
56
- obs_time : str, in JD or MJD format. If no obs_time is provided, the current
57
- time is used.
40
+
58
41
"""
59
42
60
43
super ().__init__ ()
61
- self .planet = planet
62
- self .obs_time = obs_time
63
44
64
45
def __str__ (self ):
65
46
"""
66
- String representation of RingNodeClass object instance'
47
+ String representation of RingNodeClass object instance
67
48
68
49
Examples
69
50
--------
70
51
>>> from astroquery.solarsystem.pds import RingNode
71
- >>> uranus = RingNode(planet='Uranus',
72
- ... obs_time='2017-01-01 00:00')
73
- >>> print(uranus) # doctest: +SKIP
74
- PDSRingNode instance "Uranus"; obs_time='2017-01-01 00:00'
52
+ >>> nodeobj = RingNode()
53
+ >>> print(nodeobj) # doctest: +SKIP
54
+ PDSRingNode instance
75
55
"""
76
- return ('PDSRingNode instance "{:s}"; obs_time={:s}' ).format (
77
- str (self .planet ), str (self .obs_time )
78
- )
56
+ return 'PDSRingNode instance'
79
57
80
58
# --- pretty stuff above this line, get it working below this line ---
81
59
82
60
def ephemeris_async (
83
61
self ,
84
- observer_coords = None ,
62
+ planet ,
63
+ obs_time = None ,
64
+ location = None ,
85
65
neptune_arcmodel = 3 ,
86
66
get_query_payload = False ,
87
67
get_raw_response = False ,
@@ -95,7 +75,22 @@ def ephemeris_async(
95
75
Parameters
96
76
----------
97
77
self : RingNodeClass instance
98
- observer_coords : three-element list/array/tuple of format (lat (deg), lon (deg east), altitude (m))
78
+ planet : str, required. one of Mars, Jupiter, Saturn, Uranus, Neptune, or Pluto
79
+ obs_time : astropy.Time object, or str in format YYYY-MM-DD hh:mm, optional.
80
+ If str is provided then UTC is assumed. If no obs_time is provided,
81
+ the current time is used.
82
+ location : array-like, or `~astropy.coordinates.EarthLocation`, optional
83
+ Observer's location as a
84
+ 3-element array of Earth longitude, latitude, altitude, or
85
+ a `~astropy.coordinates.EarthLocation`. Longitude and
86
+ latitude should be anything that initializes an
87
+ `~astropy.coordinates.Angle` object, and altitude should
88
+ initialize an `~astropy.units.Quantity` object (with units
89
+ of length). If ``None``, then the geocenter (code 500) is
90
+ used.
91
+ neptune_arcmodel : float, optional. which ephemeris to assume for Neptune's ring arcs
92
+ must be one of 1, 2, or 3 (see https://pds-rings.seti.org/tools/viewer3_nep.shtml for details)
93
+ has no effect if planet != 'Neptune'
99
94
100
95
Returns
101
96
-------
@@ -105,22 +100,24 @@ def ephemeris_async(
105
100
Examples
106
101
--------
107
102
>>> from astroquery.solarsystem.pds import RingNode
108
- >>> uranus = RingNode(planet='Uranus',
109
- ... obs_time='2017-01-01 00:00')
110
- >>> eph = obj.ephemeris( ) # doctest: +SKIP
103
+ >>> nodeobj = RingNode()
104
+ >>> eph = obj.ephemeris(planet='Uranus',
105
+ ... obs_time='2017-01-01 00:00' ) # doctest: +SKIP
111
106
>>> print(eph) # doctest: +SKIP
112
107
table here...
113
108
"""
109
+ planet = planet
110
+ obs_time = obs_time
114
111
115
112
URL = conf .pds_server
116
113
# URL = 'https://pds-rings.seti.org/cgi-bin/tools/viewer3_xxx.pl?'
117
114
118
115
# check inputs and set defaults for optional inputs
119
- if self . planet is None :
116
+ if planet is None :
120
117
raise ValueError ("'planet' parameter not set. Query aborted." )
121
118
else :
122
- self . planet = self . planet .lower ()
123
- if self . planet not in [
119
+ planet = planet .lower ()
120
+ if planet not in [
124
121
"mars" ,
125
122
"jupiter" ,
126
123
"saturn" ,
@@ -132,31 +129,50 @@ def ephemeris_async(
132
129
"illegal value for 'planet' parameter (must be 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', or 'Pluto'"
133
130
)
134
131
135
- if self . obs_time is None :
136
- self . obs_time = Time .now ().strftime ("%Y-%m-%d %H:%M" )
132
+ if obs_time is None :
133
+ obs_time = Time .now ().strftime ("%Y-%m-%d %H:%M" )
137
134
warnings .warn ("obs_time not set. using current time instead." )
138
- else :
135
+ elif type ( obs_time ) == str :
139
136
try :
140
- Time .strptime (self . obs_time , "%Y-%m-%d %H:%M" ).jd
137
+ Time .strptime (obs_time , "%Y-%m-%d %H:%M" ).jd
141
138
except Exception as e :
142
139
raise ValueError (
143
- "illegal value for 'obs_time' parameter. must have format 'yyyy-mm-dd hh:mm'"
140
+ "illegal value for 'obs_time' parameter. string must have format 'yyyy-mm-dd hh:mm'"
141
+ )
142
+ elif type (obs_time ) == Time :
143
+ try :
144
+ obs_time = obs_time .utc .to_value ('iso' , subfmt = 'date_hm' )
145
+ except Exception as e :
146
+ raise ValueError (
147
+ "illegal value for 'obs_time' parameter. could not parse astropy.time.core.Time object into format 'yyyy-mm-dd hh:mm' (UTC)"
144
148
)
145
149
146
- if observer_coords is None :
150
+ if location is None :
147
151
viewpoint = "observatory"
148
152
latitude , longitude , altitude = "" , "" , ""
149
153
print ("Observatory coordinates not set. Using center of Earth." )
150
154
else :
151
155
viewpoint = "latlon"
152
- try :
153
- latitude , longitude , altitude = [float (j ) for j in observer_coords ]
154
- assert - 90.0 <= latitude <= 90.0
155
- assert - 360.0 <= longitude <= 360.0
156
- except Exception as e :
157
- raise ValueError (
158
- f"Illegal observatory coordinates { observer_coords } . must be of format [lat(deg), lon(deg east), alt(m)]"
159
- )
156
+ if type (location ) != EarthLocation :
157
+ if hasattr (location , '__iter__' ):
158
+ if len (location ) != 3 :
159
+ raise ValueError (
160
+ "location arrays require three values:"
161
+ " longitude, latitude, and altitude" )
162
+ else :
163
+ raise TypeError (
164
+ "location must be array-like or astropy EarthLocation" )
165
+
166
+ if isinstance (location , EarthLocation ):
167
+ loc = location .geodetic
168
+ longitude = loc [0 ].deg
169
+ latitude = loc [1 ].deg
170
+ altitude = loc [2 ].to (u .m ).value
171
+ elif hasattr (location , '__iter__' ):
172
+ longitude = Angle (location [0 ]).deg
173
+ latitude = Angle (location [1 ]).deg
174
+ altitude = u .Quantity (location [2 ]).to ('m' ).value
175
+
160
176
if int (neptune_arcmodel ) not in [1 , 2 , 3 ]:
161
177
raise ValueError (
162
178
f"Illegal Neptune arc model { neptune_arcmodel } . must be one of 1, 2, or 3 (see https://pds-rings.seti.org/tools/viewer3_nep.shtml for details)"
@@ -167,20 +183,20 @@ def ephemeris_async(
167
183
# thankfully, adding extra planet-specific keywords here does not break query for other planets
168
184
request_payload = OrderedDict (
169
185
[
170
- ("abbrev" , self . planet [:3 ]),
186
+ ("abbrev" , planet [:3 ]),
171
187
(
172
188
"ephem" ,
173
- conf .planet_defaults [self . planet ]["ephem" ],
189
+ conf .planet_defaults [planet ]["ephem" ],
174
190
), # change hardcoding for other planets
175
- ("time" , self . obs_time ),
191
+ ("time" , obs_time ), #UTC. this should be enforced when checking inputs
176
192
(
177
193
"fov" ,
178
194
10 ,
179
195
), # next few are figure options, can be hardcoded and ignored
180
- ("fov_unit" , self . planet .capitalize () + " radii" ),
196
+ ("fov_unit" , planet .capitalize () + " radii" ),
181
197
("center" , "body" ),
182
- ("center_body" , self . planet .capitalize ()),
183
- ("center_ansa" , conf .planet_defaults [self . planet ]["center_ansa" ]),
198
+ ("center_body" , planet .capitalize ()),
199
+ ("center_ansa" , conf .planet_defaults [planet ]["center_ansa" ]),
184
200
("center_ew" , "east" ),
185
201
("center_ra" , "" ),
186
202
("center_ra_type" , "hours" ),
@@ -195,8 +211,8 @@ def ephemeris_async(
195
211
("longitude" , longitude ),
196
212
("lon_dir" , "east" ),
197
213
("altitude" , altitude ),
198
- ("moons" , conf .planet_defaults [self . planet ]["moons" ]),
199
- ("rings" , conf .planet_defaults [self . planet ]["rings" ]),
214
+ ("moons" , conf .planet_defaults [planet ]["moons" ]),
215
+ ("rings" , conf .planet_defaults [planet ]["rings" ]),
200
216
("arcmodel" , conf .neptune_arcmodels [int (neptune_arcmodel )]),
201
217
(
202
218
"extra_ra" ,
@@ -262,7 +278,6 @@ def _parse_ringnode(self, src):
262
278
# input parameters. only thing needed is obs_time
263
279
if group .startswith ("Observation" ):
264
280
obs_time = group .split ("\n " )[0 ].split ("e: " )[- 1 ].strip (", \n " )
265
- self .obs_time = obs_time
266
281
267
282
# minor body table part 1
268
283
elif group .startswith ("Body" ):
@@ -282,7 +297,18 @@ def _parse_ringnode(self, src):
282
297
"dRA" ,
283
298
"dDec" ,
284
299
),
285
- )
300
+ )
301
+ units_list = [None ,
302
+ None ,
303
+ None ,
304
+ None ,
305
+ u .deg ,
306
+ u .deg ,
307
+ u .arcsec ,
308
+ u .arcsec ]
309
+ bodytable = table .QTable (bodytable , units = units_list )
310
+ #for i in range(len(bodytable.colnames)):
311
+ # bodytable[bodytable.colnames[i]].unit = units_list[i]
286
312
# minor body table part 2
287
313
elif group .startswith ("Sub-" ):
288
314
group = "\n " .join (group .split ("\n " )[1 :]) # fixing two-row header
@@ -301,8 +327,19 @@ def _parse_ringnode(self, src):
301
327
"sub_sun_lat" ,
302
328
"phase" ,
303
329
"distance" ,
304
- ),
305
- )
330
+ ))
331
+ units_list = [
332
+ None ,
333
+ None ,
334
+ u .deg ,
335
+ u .deg ,
336
+ u .deg ,
337
+ u .deg ,
338
+ u .deg ,
339
+ u .km * 1e6 ]
340
+ bodytable2 = table .QTable (bodytable2 , units = units_list )
341
+ #for i in range(len(bodytable2.colnames)):
342
+ # bodytable2[bodytable2.colnames[i]].unit = units_list[i]
306
343
307
344
# ring plane data
308
345
elif group .startswith ("Ring s" ):
@@ -316,23 +353,23 @@ def _parse_ringnode(self, src):
316
353
# systemtable = table.Table([[sub_sun_lat], [sub_sun_lat_max], [sub_sun_lat_min]],
317
354
# names = ('sub_sun_lat', 'sub_sun_lat_max', 'sub_sun_lat_min'))
318
355
systemtable = {
319
- "sub_sun_lat" : sub_sun_lat ,
320
- "sub_sun_lat_min" : sub_sun_lat_min ,
321
- "sub_sun_lat_max" : sub_sun_lat_min ,
356
+ "sub_sun_lat" : sub_sun_lat * u . deg ,
357
+ "sub_sun_lat_min" : sub_sun_lat_min * u . deg ,
358
+ "sub_sun_lat_max" : sub_sun_lat_min * u . deg ,
322
359
}
323
360
324
361
elif "Ring plane opening angle" in l [0 ]:
325
362
systemtable ["opening_angle" ] = float (
326
363
re .sub ("[a-zA-Z]+" , "" , l [1 ]).strip (", \n ()" )
327
- )
364
+ ) * u . deg
328
365
elif "Ring center phase angle" in l [0 ]:
329
- systemtable ["phase_angle" ] = float (l [1 ].strip (", \n " ))
366
+ systemtable ["phase_angle" ] = float (l [1 ].strip (", \n " )) * u . deg
330
367
elif "Sub-solar longitude" in l [0 ]:
331
368
systemtable ["sub_sun_lon" ] = float (
332
369
re .sub ("[a-zA-Z]+" , "" , l [1 ]).strip (", \n ()" )
333
- )
370
+ ) * u . deg
334
371
elif "Sub-observer longitude" in l [0 ]:
335
- systemtable ["sub_obs_lon" ] = float (l [1 ].strip (", \n " ))
372
+ systemtable ["sub_obs_lon" ] = float (l [1 ].strip (", \n " )) * u . deg
336
373
else :
337
374
pass
338
375
@@ -342,19 +379,21 @@ def _parse_ringnode(self, src):
342
379
for line in lines :
343
380
l = line .split (":" )
344
381
if "Sun-planet distance (AU)" in l [0 ]:
345
- systemtable ["d_sun_AU" ] = float (l [1 ].strip (", \n " ))
382
+ #systemtable["d_sun_AU"] = float(l[1].strip(", \n"))
383
+ pass
346
384
elif "Observer-planet distance (AU)" in l [0 ]:
347
- systemtable ["d_obs_AU" ] = float (l [1 ].strip (", \n " ))
385
+ #systemtable["d_obs_AU"] = float(l[1].strip(", \n"))
386
+ pass
348
387
elif "Sun-planet distance (km)" in l [0 ]:
349
- systemtable ["d_sun_km " ] = (
350
- float (l [1 ].split ("x" )[0 ].strip (", \n " )) * 1e6
388
+ systemtable ["d_sun " ] = (
389
+ float (l [1 ].split ("x" )[0 ].strip (", \n " )) * 1e6 * u . km
351
390
)
352
391
elif "Observer-planet distance (km)" in l [0 ]:
353
- systemtable ["d_obs_km " ] = (
354
- float (l [1 ].split ("x" )[0 ].strip (", \n " )) * 1e6
392
+ systemtable ["d_obs " ] = (
393
+ float (l [1 ].split ("x" )[0 ].strip (", \n " )) * 1e6 * u . km
355
394
)
356
395
elif "Light travel time" in l [0 ]:
357
- systemtable ["light_time" ] = float (l [1 ].strip (", \n " ))
396
+ systemtable ["light_time" ] = float (l [1 ].strip (", \n " )) * u . second
358
397
else :
359
398
pass
360
399
@@ -366,10 +405,11 @@ def _parse_ringnode(self, src):
366
405
format = "fixed_width" ,
367
406
col_starts = (5 , 18 , 29 ),
368
407
col_ends = (18 , 29 , 36 ),
369
- names = ("ring" , "pericenter" , "ascending node" ),
370
- )
371
- ringtable .add_index ("ring" )
372
-
408
+ names = ("ring" , "pericenter" , "ascending node" ))
409
+
410
+ units_list = [None , u .deg , u .deg ]
411
+ ringtable = table .QTable (ringtable , units = units_list )
412
+
373
413
# Saturn F-ring data
374
414
elif group .startswith ("F Ring" ):
375
415
lines = group .split ("\n " )
@@ -382,6 +422,7 @@ def _parse_ringnode(self, src):
382
422
ringtable = table .Table (
383
423
[["F" ], [peri ], [ascn ]],
384
424
names = ("ring" , "pericenter" , "ascending node" ),
425
+ units = (None , u .deg , u .deg )
385
426
)
386
427
387
428
# Neptune ring arcs data
@@ -398,16 +439,24 @@ def _parse_ringnode(self, src):
398
439
ringtable = table .Table (
399
440
[[ring ], [min_angle ], [max_angle ]],
400
441
names = ("ring" , "min_angle" , "max_angle" ),
442
+ units = (None , u .deg , u .deg )
401
443
)
402
444
else :
403
445
ringtable .add_row ([ring , min_angle , max_angle ])
404
446
405
447
else :
406
448
pass
407
449
408
- # concatenate minor body table
409
- bodytable = table .join (bodytable , bodytable2 )
450
+ #
451
+ ## do some cleanup from the parsing job
452
+ #
453
+ ringtable .add_index ("ring" )
454
+
455
+ bodytable = table .join (bodytable , bodytable2 ) # concatenate minor body table
410
456
bodytable .add_index ("Body" )
457
+
458
+ systemtable ["obs_time" ] = Time (obs_time , format = 'iso' , scale = 'utc' ) # add obs time to systemtable
459
+
411
460
412
461
return systemtable , bodytable , ringtable
413
462
0 commit comments