Skip to content

Commit d4b1f02

Browse files
emolterbsipocz
authored andcommitted
added units, some fixes suggested by mkelley
1 parent b7d2751 commit d4b1f02

File tree

4 files changed

+18817
-124
lines changed

4 files changed

+18817
-124
lines changed

astroquery/solarsystem/pds/core.py

Lines changed: 133 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from astropy.time import Time
99
from astropy import table
1010
from astropy.io import ascii
11+
import astropy.units as u
12+
from astropy.coordinates import EarthLocation, Angle
1113
from bs4 import BeautifulSoup
1214

1315
# 3. local imports - use relative imports
@@ -26,22 +28,6 @@ class RingNodeClass(BaseQuery):
2628
for querying the Planetary Ring Node ephemeris tools
2729
<https://pds-rings.seti.org/tools/>
2830
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-
4531
"""
4632

4733
TIMEOUT = conf.timeout
@@ -51,37 +37,31 @@ def __init__(self, planet=None, obs_time=None):
5137
5238
Parameters
5339
----------
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+
5841
"""
5942

6043
super().__init__()
61-
self.planet = planet
62-
self.obs_time = obs_time
6344

6445
def __str__(self):
6546
"""
66-
String representation of RingNodeClass object instance'
47+
String representation of RingNodeClass object instance
6748
6849
Examples
6950
--------
7051
>>> 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
7555
"""
76-
return ('PDSRingNode instance "{:s}"; obs_time={:s}').format(
77-
str(self.planet), str(self.obs_time)
78-
)
56+
return 'PDSRingNode instance'
7957

8058
# --- pretty stuff above this line, get it working below this line ---
8159

8260
def ephemeris_async(
8361
self,
84-
observer_coords=None,
62+
planet,
63+
obs_time=None,
64+
location=None,
8565
neptune_arcmodel=3,
8666
get_query_payload=False,
8767
get_raw_response=False,
@@ -95,7 +75,22 @@ def ephemeris_async(
9575
Parameters
9676
----------
9777
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'
9994
10095
Returns
10196
-------
@@ -105,22 +100,24 @@ def ephemeris_async(
105100
Examples
106101
--------
107102
>>> 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
111106
>>> print(eph) # doctest: +SKIP
112107
table here...
113108
"""
109+
planet = planet
110+
obs_time = obs_time
114111

115112
URL = conf.pds_server
116113
# URL = 'https://pds-rings.seti.org/cgi-bin/tools/viewer3_xxx.pl?'
117114

118115
# check inputs and set defaults for optional inputs
119-
if self.planet is None:
116+
if planet is None:
120117
raise ValueError("'planet' parameter not set. Query aborted.")
121118
else:
122-
self.planet = self.planet.lower()
123-
if self.planet not in [
119+
planet = planet.lower()
120+
if planet not in [
124121
"mars",
125122
"jupiter",
126123
"saturn",
@@ -132,31 +129,50 @@ def ephemeris_async(
132129
"illegal value for 'planet' parameter (must be 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', or 'Pluto'"
133130
)
134131

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")
137134
warnings.warn("obs_time not set. using current time instead.")
138-
else:
135+
elif type(obs_time) == str:
139136
try:
140-
Time.strptime(self.obs_time, "%Y-%m-%d %H:%M").jd
137+
Time.strptime(obs_time, "%Y-%m-%d %H:%M").jd
141138
except Exception as e:
142139
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)"
144148
)
145149

146-
if observer_coords is None:
150+
if location is None:
147151
viewpoint = "observatory"
148152
latitude, longitude, altitude = "", "", ""
149153
print("Observatory coordinates not set. Using center of Earth.")
150154
else:
151155
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+
160176
if int(neptune_arcmodel) not in [1, 2, 3]:
161177
raise ValueError(
162178
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(
167183
# thankfully, adding extra planet-specific keywords here does not break query for other planets
168184
request_payload = OrderedDict(
169185
[
170-
("abbrev", self.planet[:3]),
186+
("abbrev", planet[:3]),
171187
(
172188
"ephem",
173-
conf.planet_defaults[self.planet]["ephem"],
189+
conf.planet_defaults[planet]["ephem"],
174190
), # change hardcoding for other planets
175-
("time", self.obs_time),
191+
("time", obs_time), #UTC. this should be enforced when checking inputs
176192
(
177193
"fov",
178194
10,
179195
), # next few are figure options, can be hardcoded and ignored
180-
("fov_unit", self.planet.capitalize() + " radii"),
196+
("fov_unit", planet.capitalize() + " radii"),
181197
("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"]),
184200
("center_ew", "east"),
185201
("center_ra", ""),
186202
("center_ra_type", "hours"),
@@ -195,8 +211,8 @@ def ephemeris_async(
195211
("longitude", longitude),
196212
("lon_dir", "east"),
197213
("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"]),
200216
("arcmodel", conf.neptune_arcmodels[int(neptune_arcmodel)]),
201217
(
202218
"extra_ra",
@@ -262,7 +278,6 @@ def _parse_ringnode(self, src):
262278
# input parameters. only thing needed is obs_time
263279
if group.startswith("Observation"):
264280
obs_time = group.split("\n")[0].split("e: ")[-1].strip(", \n")
265-
self.obs_time = obs_time
266281

267282
# minor body table part 1
268283
elif group.startswith("Body"):
@@ -282,7 +297,18 @@ def _parse_ringnode(self, src):
282297
"dRA",
283298
"dDec",
284299
),
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]
286312
# minor body table part 2
287313
elif group.startswith("Sub-"):
288314
group = "\n".join(group.split("\n")[1:]) # fixing two-row header
@@ -301,8 +327,19 @@ def _parse_ringnode(self, src):
301327
"sub_sun_lat",
302328
"phase",
303329
"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]
306343

307344
# ring plane data
308345
elif group.startswith("Ring s"):
@@ -316,23 +353,23 @@ def _parse_ringnode(self, src):
316353
# systemtable = table.Table([[sub_sun_lat], [sub_sun_lat_max], [sub_sun_lat_min]],
317354
# names = ('sub_sun_lat', 'sub_sun_lat_max', 'sub_sun_lat_min'))
318355
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,
322359
}
323360

324361
elif "Ring plane opening angle" in l[0]:
325362
systemtable["opening_angle"] = float(
326363
re.sub("[a-zA-Z]+", "", l[1]).strip(", \n()")
327-
)
364+
) * u.deg
328365
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
330367
elif "Sub-solar longitude" in l[0]:
331368
systemtable["sub_sun_lon"] = float(
332369
re.sub("[a-zA-Z]+", "", l[1]).strip(", \n()")
333-
)
370+
) * u.deg
334371
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
336373
else:
337374
pass
338375

@@ -342,19 +379,21 @@ def _parse_ringnode(self, src):
342379
for line in lines:
343380
l = line.split(":")
344381
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
346384
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
348387
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
351390
)
352391
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
355394
)
356395
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
358397
else:
359398
pass
360399

@@ -366,10 +405,11 @@ def _parse_ringnode(self, src):
366405
format="fixed_width",
367406
col_starts=(5, 18, 29),
368407
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+
373413
# Saturn F-ring data
374414
elif group.startswith("F Ring"):
375415
lines = group.split("\n")
@@ -382,6 +422,7 @@ def _parse_ringnode(self, src):
382422
ringtable = table.Table(
383423
[["F"], [peri], [ascn]],
384424
names=("ring", "pericenter", "ascending node"),
425+
units=(None, u.deg, u.deg)
385426
)
386427

387428
# Neptune ring arcs data
@@ -398,16 +439,24 @@ def _parse_ringnode(self, src):
398439
ringtable = table.Table(
399440
[[ring], [min_angle], [max_angle]],
400441
names=("ring", "min_angle", "max_angle"),
442+
units=(None, u.deg, u.deg)
401443
)
402444
else:
403445
ringtable.add_row([ring, min_angle, max_angle])
404446

405447
else:
406448
pass
407449

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
410456
bodytable.add_index("Body")
457+
458+
systemtable["obs_time"] = Time(obs_time, format = 'iso', scale = 'utc') # add obs time to systemtable
459+
411460

412461
return systemtable, bodytable, ringtable
413462

0 commit comments

Comments
 (0)