Skip to content

Commit a4ab3a5

Browse files
committed
Allow using units for location coordinates in jplhorizons
1 parent f6868ca commit a4ab3a5

File tree

2 files changed

+63
-33
lines changed

2 files changed

+63
-33
lines changed

astroquery/jplhorizons/core.py

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from astropy.table import Table, Column
1414
from astropy.io import ascii
1515
from astropy.time import Time
16+
from astropy import units as u
1617
from astropy.utils.exceptions import AstropyDeprecationWarning
1718
from astropy.utils.decorators import deprecated_renamed_argument, deprecated_attribute
1819

@@ -53,14 +54,14 @@ def __init__(self, id=None, *, location=None, epochs=None,
5354
5455
id : str or dict, required
5556
Name, number, or designation of target object. Uses the same codes
56-
as JPL Horizons. Arbitrary topocentric coordinates can be added
57-
in a dict. The dict has to be of the form
58-
{``'lon'``: longitude in deg (East positive, West
59-
negative), ``'lat'``: latitude in deg (North positive, South
60-
negative), ``'elevation'``: elevation in km above the reference
61-
ellipsoid, [``'body'``: Horizons body ID of the central body;
62-
optional; if this value is not provided it is assumed that this
63-
location is on Earth]}.
57+
as JPL Horizons. Arbitrary topocentric coordinates can be added in a
58+
dict. The dict has to be of the form {``'lon'``: longitude in deg
59+
(East positive, West negative), ``'lat'``: latitude in deg (North
60+
positive, South negative), ``'elevation'``: elevation in km above
61+
the reference ellipsoid, [``'body'``: Horizons body ID of the
62+
central body; optional; if this value is not provided it is assumed
63+
that this location is on Earth]}. Float values are assumed to have
64+
units of degrees and kilometers.
6465
6566
location : str or dict, optional
6667
Observer's location for ephemerides queries or center body name for
@@ -69,12 +70,13 @@ def __init__(self, id=None, *, location=None, epochs=None,
6970
ephemerides queries and the Sun's center for elements and vectors
7071
queries. Arbitrary topocentric coordinates for ephemerides queries
7172
can be provided in the format of a dictionary. The dictionary has to
72-
be of the form {``'lon'``: longitude in deg (East positive, West
73-
negative), ``'lat'``: latitude in deg (North positive, South
74-
negative), ``'elevation'``: elevation in km above the reference
75-
ellipsoid, [``'body'``: Horizons body ID of the central body;
76-
optional; if this value is not provided it is assumed that this
77-
location is on Earth]}.
73+
be of the form {``'lon'``: longitude (East positive, West negative),
74+
``'lat'``: latitude (North positive, South negative),
75+
``'elevation'``: elevation above the reference ellipsoid,
76+
[``'body'``: Horizons body ID of the central body; optional; if this
77+
value is not provided it is assumed that this location is on
78+
Earth]}. Float values are assumed to have units of degrees and
79+
kilometers.
7880
7981
epochs : scalar, list-like, or dictionary, optional
8082
Either a list of epochs in JD or MJD format or a dictionary defining
@@ -117,16 +119,10 @@ def __init__(self, id=None, *, location=None, epochs=None,
117119
"""
118120

119121
super().__init__()
120-
# check & format coordinate dictionaries for id and location; simply
121-
# treat other values as given
122-
if isinstance(id, Mapping):
123-
self.id = self._prep_loc_dict(dict(id), "id")
124-
else:
125-
self.id = id
126-
if isinstance(location, Mapping):
127-
self.location = self._prep_loc_dict(dict(location), "location")
128-
else:
129-
self.location = location
122+
123+
self.id = id
124+
self.location = location
125+
130126
# check for epochs to be dict or list-like; else: make it a list
131127
if epochs is not None:
132128
if isinstance(epochs, (list, tuple, ndarray)):
@@ -185,6 +181,32 @@ def __str__(self):
185181
str(self.epochs),
186182
str(self.id_type))
187183

184+
@property
185+
def id(self):
186+
return self._id
187+
188+
@id.setter
189+
def id(self, _id):
190+
# check & format coordinate dictionaries for id; simply treat other
191+
# values as given
192+
if isinstance(_id, Mapping):
193+
self._id = self._prep_loc_dict(dict(_id), "id")
194+
else:
195+
self._id = _id
196+
197+
@property
198+
def location(self):
199+
return self._location
200+
201+
@location.setter
202+
def location(self, _location):
203+
# check & format coordinate dictionaries for location; simply treat
204+
# other values as given
205+
if isinstance(_location, Mapping):
206+
self._location = self._prep_loc_dict(dict(_location), "location")
207+
else:
208+
self._location = _location
209+
188210
# ---------------------------------- query functions
189211

190212
@deprecated_renamed_argument("get_raw_response", None, since="0.4.7",
@@ -593,10 +615,7 @@ def ephemerides_async(self, *, airmass_lessthan=99,
593615
if self.id is None:
594616
raise ValueError("'id' parameter not set. Query aborted.")
595617
elif isinstance(self.id, dict):
596-
commandline = (
597-
f"g:{self.id['lon']},{self.id['lat']},"
598-
f"{self.id['elevation']}@{self.id['body']}"
599-
)
618+
commandline = "g:" + self._format_site_coords(self.id)
600619
else:
601620
commandline = str(self.id)
602621
if self.location is None:
@@ -1080,7 +1099,7 @@ def vectors_async(self, *, get_query_payload=False,
10801099
if self.id is None:
10811100
raise ValueError("'id' parameter not set. Query aborted.")
10821101
elif isinstance(self.id, dict):
1083-
commandline = "g:{lon},{lat},{elevation}@{body}".format(**self.id)
1102+
commandline = "g:" + self._format_site_coords(self.id)
10841103
else:
10851104
commandline = str(self.id)
10861105
if self.location is None:
@@ -1183,6 +1202,10 @@ def _prep_loc_dict(loc_dict, attr_name):
11831202
)
11841203
if 'body' not in loc_dict:
11851204
loc_dict['body'] = 399
1205+
# assumed units are degrees and km
1206+
loc_dict["lat"] = u.Quantity(loc_dict["lat"], u.deg)
1207+
loc_dict["lon"] = u.Quantity(loc_dict["lon"], u.deg)
1208+
loc_dict["elevation"] = u.Quantity(loc_dict["elevation"], u.km)
11861209
return loc_dict
11871210

11881211
@staticmethod
@@ -1191,12 +1214,19 @@ def _location_to_params(loc_dict):
11911214
loc_dict = {
11921215
"CENTER": f"coord@{loc_dict['body']}",
11931216
"COORD_TYPE": "GEODETIC",
1194-
"SITE_COORD": ",".join(
1195-
str(float(loc_dict[k])) for k in ['lon', 'lat', 'elevation']
1196-
)
1217+
"SITE_COORD": HorizonsClass._format_site_coords(loc_dict)
11971218
}
11981219
loc_dict["SITE_COORD"] = f"'{loc_dict['SITE_COORD']}'"
11991220
return loc_dict
1221+
1222+
@staticmethod
1223+
def _format_site_coords(coords):
1224+
# formats lon/lat/elevation/body (e.g., id and location dictionaries) for the Horizons API
1225+
",".join(
1226+
str(coords[k].to_value()) for k in ['lon', 'lat', 'elevation']
1227+
)
1228+
return (f"{coords['lon'].to_value('deg')},{coords['lat'].to_value('deg')},"
1229+
f"{coords['elevation'].to_value('km')}@{coords['body']}")
12001230

12011231
def _parse_result(self, response, verbose=None):
12021232
"""

docs/jplhorizons/jplhorizons.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ as the observer's location, and Ceres as the target:
103103
... location=statue_of_liberty,
104104
... epochs=2458133.33546)
105105
>>> print(obj)
106-
JPLHorizons instance "Ceres"; location={'lon': -74.0466891, 'lat': 40.6892534, 'elevation': 0.093, 'body': 399}, epochs=[2458133.33546], id_type=None
106+
JPLHorizons instance "Ceres"; location={'lon': <Quantity -74.0466891 deg>, 'lat': <Quantity 40.6892534 deg>, 'elevation': <Quantity 0.093 km>, 'body': 399}, epochs=[2458133.33546], id_type=None
107107

108108
2. Specifying topocentric coordinates for both location and observer is often
109109
useful when performing geometric calculations for artificial satellites without

0 commit comments

Comments
 (0)