# Python API for JPL Horizons?

I found the python package HorizonJPL in the Python Package Index, but it looks like it's limited to activity in 2013. When I go to the linked documentation page https://docs.google.com/document/d/1g9q3ln9LVAATOZ15986HLTCaqcAj_Jd8e_jOGS3YWrE/pub the info is fairly sparse, and the two links there lead to a dead end in Japanese (below).

Is there a way I can access JPL Horizons for tables of data (Like I am doing here) from within a Python script? In other words, I want to build a url query and receive a json of text with state vectors in return using urllib or urllib2.

Update: This is a typical example of what I want to do:

This is what is displayed on the pypi site - there seems to be names/handles but I don't know how to query them.

## API Documentation

https://docs.google.com/document/d/1g9q3ln9LVAATOZ15986HLTCaqcAj_Jd8e_jOGS3YWrE/pub

## Resources

Planetary Data System: http://pds.nasa.gov/

Jet Propulsion Labs: http://www.jpl.nasa.gov/

HORIZON User Manual: http://ssd.jpl.nasa.gov/?horizons_doc

Contributors

Matthew Mihok (@mattmattmatt)

Dexter Jagula (@djagula)

Siddarth Kalra (@SiddarthKalra)

Tiago Moreira (@Kamots)

Here is what happens when I try the links in the google doc:

• If this doesn't attract any help in say another week, then maybe a good candidate for migration to stackoverflow at that time. The python tag is critical there. – uhoh Jun 4 '16 at 11:33
• Have you tried using telnetlib to access the telnet interface? – 2012rcampion Jun 4 '16 at 20:25
• @2012rcampion OK - I'll look into it today! If I understand correctly, I would have to write a python script that basically emulates a person typing commands and receiving prompts. It's possible someone has done a good job of this and written a nice interface wrapper - my ultimate goal is to see if anyone has made something like that generally available. In the mean time I will definitely try what you have suggested. Thanks!! – uhoh Jun 5 '16 at 2:03
• If you're not intending to use the script for a long time, you don't have to process the prompts at all. What I did was to get one dataset manually, then have my script basically replay the same session with one configurable parameter. – 2012rcampion Jun 5 '16 at 4:44
• @2012rcampion OK got it. The manual is helpful, but I'm searching for example sessions, which are easier for me to digest. I've added an example of what I want to do in the question. If I get it working, I'll post it as an answer - if someone else posts a similar example telnet session or a script first, gosh I'll be forced to accept it! :) – uhoh Jun 5 '16 at 8:44

## 3 Answers

callhorizons is depricated now and refers to the python library astroquery which now seems to be the way to go.

astroquery (GitHub, readthedocs) is "an astropy affiliated package that contains a collection of tools to access online Astronomical data. Each web service has its own sub-package.", where making Horizons queries is just one of many options. It's part of the larger Astropy Project which is "a community effort to develop a common core package for Astronomy in Python and foster an ecosystem of interoperable astronomy packages."

I recommend reading documentation page for JPL Horizons queries for astroquery can be found here: https://astroquery.readthedocs.io/en/latest/jplhorizons/jplhorizons.html It lists a few good examples, e.g.:

from astroquery.jplhorizons import Horizons
obj = Horizons(id='Ceres', location='568',
epochs={'start':'2010-01-01', 'stop':'2010-03-01', 'step':'10d'})
eph = obj.ephemerides()
print(eph)

targetname    datetime_str   datetime_jd ...   GlxLat  RA_3sigma DEC_3sigma
---            ---             d      ...    deg      arcsec    arcsec
---------- ----------------- ----------- ... --------- --------- ----------
1 Ceres 2010-Jan-01 00:00   2455197.5 ... 24.120057       0.0        0.0
1 Ceres 2010-Jan-11 00:00   2455207.5 ... 20.621496       0.0        0.0
1 Ceres 2010-Jan-21 00:00   2455217.5 ... 17.229529       0.0        0.0
1 Ceres 2010-Jan-31 00:00   2455227.5 ...  13.97264       0.0        0.0
1 Ceres 2010-Feb-10 00:00   2455237.5 ... 10.877201       0.0        0.0
1 Ceres 2010-Feb-20 00:00   2455247.5 ...  7.976737       0.0        0.0


I also recommend skimming the docs of the HorizonsClass. In particular, I was confused initially that setting target id=399 didn't target Earth, but that was because the class also takes a id-type argument that defaults to smallbody (asteroids and the like), and needed to be changed to majorbody.

It seems very straight forward to use, and is clearly the way to go now.

PS: I will mention I got an error KeyError: 'Obsrv-lon' for my particular query, but the issue is being investigated.

There is one now! I've just run across the python package callhorizons in github and Python Package Index.

There is more information in readthedocis.io which helps explain how to do queries if you are looking for asteroids/minor bodies, for which there are zillions and the names can be people's name, or alphanumerics.

I'll try to test it further and update here. It looks like there is a lot of capability here, but I can't say for sure yet. There is a blog post by an astronomer but it's from January 2016.

It was a one-line install, namely pip install callhorizons. I then typed in the simple demo at the top of the PyPI page and got an answer!

>>> import callhorizons
>>> eros = callhorizons.query('Eros')
>>> eros.set_discreteepochs([2457446.177083, 2457446.182343])
>>> eros.get_ephemerides(568)
2
>>> print(eros['RA'], eros['DEC'])
(array([ 292.46003,  292.46332]), array([-27.44392, -27.44335]))
>>>


Yay python!

The current version is a pure python script, and contains the comment:

CALLHORIZONS - a Python interface to access JPL HORIZONS
ephemerides and orbital elements.

This module provides a convenient python interface to the JPL
HORIZONS system by directly accessing and parsing the HORIZONS
website. Ephemerides can be obtained through get_ephemerides,
orbital elements through get_elements. Function
export2pyephem provides an interface to the PyEphem module.

michael.mommert (at) nau.edu, latest version: v1.0.5, 2017-05-05.
This code is inspired by code created by Alex Hagen.


Currently it only generates the query url for an observer table, not state vectors, but it could certainly be easily customized to get state vectors. Here is a small chunk, additional parameters are added to the url string before it's used.

### construct URL for HORIZONS query
url = "http://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=l" \
+ "&TABLE_TYPE='OBSERVER'" \
+ "&QUANTITIES='" + str(quantities) + "'" \
+ "&CSV_FORMAT='YES'" \
+ "&ANG_FORMAT='DEG'" \
+ "&CAL_FORMAT='BOTH'" \
+ "&SOLAR_ELONG='" + str(solar_elongation[0]) + "," \
+ str(solar_elongation[1]) + "'" \
+ "&CENTER='"+str(observatory_code)+"'"


OK try a spacecraft, also viewed from Mauna Kea. From JPL Horizons with the following setup:

gives (scrolls horizontally):

**********************************************************************************************************************************
Date_________JDUT, , ,R.A._(ICRF/J2000.0), DEC_(ICRF/J2000.0),  APmag, S-brt,            delta,     deldot,    S-O-T,/r,    S-T-O,
**********************************************************************************************************************************
$$SOE 2457754.500000000,*,m,20 01 24.54,-56 59 28.4, n.a., n.a., 1.7047550022E+10, 18.9796569, 36.6514,/T, 0.2976, 2457784.500000000,*,m,20 03 22.69,-56 58 00.2, n.a., n.a., 1.7080283550E+10, 6.5439542, 40.6419,/L, 0.3218, 2457814.500000000,*,m,20 05 08.36,-57 01 24.2, n.a., n.a., 1.7082950019E+10, -3.4910462, 59.2751,/L, 0.4270, 2457844.500000000,*,m,20 06 16.02,-57 09 10.1, n.a., n.a., 1.7065586218E+10, -8.4792450, 82.4692,/L, 0.4975, 2457874.500000000,*,m,20 06 30.25,-57 19 39.5, n.a., n.a., 1.7043227392E+10, -7.3046878, 106.0860,/L, 0.4870, 2457904.500000000,*,m,20 05 49.21,-57 30 32.2, n.a., n.a., 1.7031649793E+10, -0.4854246, 127.5819,/L, 0.4048, 2457934.500000000,*,m,20 04 24.98,-57 39 21.6, n.a., n.a., 1.7043463848E+10, 10.1577911, 141.9907,/L, 0.3144,$$EOE
**********************************************************************************************************************************


Compare to this script:

import callhorizons
import numpy as np

JDs = [2457754.5 + 30*i for i in range(7)] # 01-Jan-2017 00:00 UTC + n*30 days

V2  = callhorizons.query(-32, smallbody=False)     # -32 for Voyager 2

V2.set_discreteepochs(JDs)

V2.get_ephemerides(568)    # 568 for Mauna Kea

radecs = np.vstack((V2['RA'], V2['DEC']))

for JD, radec in zip(JDs, radecs.T):
print JD, radec


A spot check shows that these are the same, great! Fyi the very large apparent motion is due to parallax - the Earth shifting its perspective every month. After one year the position actually only changes by a few tenths of a degree.

2457754.5 [ 300.35226  -56.99121]
2457784.5 [ 300.84454  -56.96671]
2457814.5 [ 301.28482  -57.0234 ]
2457844.5 [ 301.56676  -57.1528 ]
2457874.5 [ 301.62605  -57.32765]
2457904.5 [ 301.45504  -57.50895]
2457934.5 [ 301.10407  -57.65601]

• I've accepted my own answer because it's acceptable. I'm quite open to further and/or different answers as well, if you have something to add please don't hesitate to post it. – uhoh Jun 17 '17 at 1:38
• Seems like a pretty cool library! I would have recommended SpiceyPy which is an interface to SPICE, and SPICE runs JPL Horizons. But then you have to rewrite all the logic. – ChrisR Jun 19 '17 at 19:34
• @ChrisR I'll take a look. Can you consider leaving a short answer mentioning SpiceyPy? Comments are by definition temporary, and even if it might not be a perfect match to "Python API for JPL Horizons?" it does do something similar enough that future readers might benefit from knowing about. Doesn't have to be a long answer, just a link and an explanation that when one wants to access Horizons automatically, one is really just accessing SPICE and so one should know that the Horizons middle-man is not always necessary. – uhoh Jun 20 '17 at 3:56

Choose your input parameters in Horizons, then click this link (link of "show batch-file data" in Horizons page): https://ssd.jpl.nasa.gov/horizons.cgi?show=1#results

You'll get something like this:

!$$SOF COMMAND= '-64' CENTER= '500@0' MAKE_EPHEM= 'YES' TABLE_TYPE= 'VECTORS' START_TIME= '2018-10-1' STOP_TIME= '2018-10-30' STEP_SIZE= '1 d' OUT_UNITS= 'AU-D' REF_PLANE= 'ECLIPTIC' REF_SYSTEM= 'J2000' VECT_CORR= 'NONE' VEC_LABELS= 'YES' VEC_DELTA_T= 'NO' CSV_FORMAT= 'NO' OBJ_DATA= 'YES' VEC_TABLE= '3' !$$EOF

Remove lines starting by "!".

Copy the remaining text in your favorite text+hex editor and make these replacements:

0d0a --> & (carriage return to "&") ' --> %27 space --> %20

You will get a single line to append to this partial URL:

https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=1&

The result is in "mixed CSV" format (i.e. CSV with garbage...)

This javascript source will convert Horizons output to a valid URL and will open it:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>NASA Horizons URL generator</title>
</head>
<script src="js/jquery.js"></script>
<script>
$$( document ).ready(function() { // After page load is complete... ("#btnGo" ).click(function() { // Associate function to the button to monitor clicking. commandsText = ('textarea#commands').val(); // Upon clicking, assign textarea content to variable. commandsText = commandsText.replace(/\r/g,'&'); // Replace carriage returns commandsText = commandsText.replace(/\n/g,'&'); // Replace carriage returns commandsText = commandsText.replace(/ /g,"%20"); // Replace spaces commandsText = commandsText.replace(/\'/g,'%27'); // Replace quotes var fullURL = "https://ssd.jpl.nasa.gov/horizons_batch.cgi?batch=1&" + commandsText; //* Prepend URL base$$('textarea#URL').val(fullURL)
window.open(fullURL); // Open Url
});
});
</script>
<body>
<a href="https://ssd.jpl.nasa.gov/horizons.cgi">Source</a>:<br>
<textarea id="commands" name="commands" rows = "30" cols = "50"></textarea><br>
<br>
Resulting URL:<br>
<textarea id="URL" name="URL" rows = "30" cols = "50"></textarea><br>
<button id="btnGo" name="btnGo">Go</button>
</body>
</html>

• added Javascript source (you also asked " I want to build a url query and receive a json of text with state vectors in return "). Unfortunately I can't see any JSON output option in Horizons. – jumpjack Oct 30 '18 at 13:29
• oh you are right, I did! That was 2.5 years ago, close to when I started. I have this allergy to curly braces so I'm only able to use Python. I think your answer is helpful and I've up voted, but there are now several Python packages that do this. I wouldn't use a JSON anymore though. Thanks again! – uhoh Oct 30 '18 at 14:14