Smithsonian / SuperNOVAS

The Naval Observatory NOVAS C astrometry library, made better
https://smithsonian.github.io/SuperNOVAS/
The Unlicense
13 stars 3 forks source link
astrometry astronomy astronomy-library astronomy-software c-language c-programming-language observatory open-source positional-astronomy

Build Status Test Static Analysis API documentation Coverage Status

CfA logo


SuperNOVAS

The NOVAS C astrometry library, made better.

SuperNOVAS is a C/C++ astronomy software library, providing high-precision astrometry such as one might need for running an observatory or a precise planetarium program. It is a fork of the Naval Observatory Vector Astrometry Software (NOVAS) C version 3.1, providing bug fixes and making it easier to use overall.

SuperNOVAS is entirely free to use without licensing restrictions. Its source code is compatible with the C99 standard, and hence should be suitable for old and new platforms alike. It is light-weight and easy to use, with full support for the IAU 2000/2006 standards for sub-microarcsecond position calculations.

This document has been updated for the v1.2 and later releases.

Table of Contents


Introduction

SuperNOVAS is a fork of the The Naval Observatory Vector Astrometry Software (NOVAS).

The primary goal of SuperNOVAS is to improve on the stock NOVAS C library via:

At the same time, SuperNOVAS aims to be fully backward compatible with the intended functionality of the upstream NOVAS C library, such that it can be used as a drop-in, build-time replacement for NOVAS in your application without having to change existing (functional) code you may have written for NOVAS C.

SuperNOVAS is currently based on NOVAS C version 3.1. We plan to rebase SuperNOVAS to the latest upstream release of the NOVAS C library, if new releases become available.

SuperNOVAS is maintained by Attila Kovacs at the Center for Astrophysics | Harvard & Smithsonian, and it is available through the Smithsonian/SuperNOVAS repository on GitHub.

Outside contributions are very welcome. See how you can contribute to make SuperNOVAS even better.

Related links


Fixed NOVAS C 3.1 issues

The SuperNOVAS library fixes a number of outstanding issues with NOVAS C 3.1. Here is a list of issues and fixes provided by SuperNOVAS over the upstream NOVAS C 3.1 code:


Compatibility with NOVAS C 3.1

SuperNOVAS strives to maintain API compatibility with the upstream NOVAS C 3.1 library, but not binary (ABI) compatibility.

If you have code that was written for NOVAS C 3.1, it should work with SuperNOVAS as is, without modifications. Simply (re)build your application against SuperNOVAS, and you are good to go.

The lack of binary compatibility just means that you cannot drop-in replace your compiled objects (e.g. novas.o, or the static libnovas.a, or the shared libnovas.so) libraries, from NOVAS C 3.1 with those from SuperNOVAS. Instead, you will have to build (compile) your application referencing the SuperNOVAS headers and/or libraries from the start.

This is because some function signatures have changed, e.g. to use an enum argument instead of the nondescript short int argument of NOVAS C 3.1, or because we added a return value to a function that was declared void in NOVAS C 3.1. We also changed the object structure to contain a long ID number instead of short to accommodate JPL NAIF codes, for which 16-bit storage is insufficient.


Building and installation

The SuperNOVAS distribution contains a GNU Makefile, which is suitable for compiling the library (as well as local documentation, and tests, etc.) on POSIX systems such as Linux, BSD, MacOS X, or Cygwin or WSL. (At this point we do not provide a similar native build setup for Windows, but speak up if you would like to add it yourself!)

Before compiling the library take a look a config.mk and edit it as necessary for your needs, or else define the necessary variables in the shell prior to invoking make. For example:

Now you are ready to build the library:

  $ make

will compile the shared (e.g. lib/libsupernovas.so) libraries, produce a CIO locator data file (e.g. tools/data/cio_ra.bin), and compile the API documentation (into apidoc/) using doxygen (if available). Alternatively, you can build select components of the above with the make targets shared, and local-dox respectively. And, if unsure, you can always call make help to see what build targets are available.

After building the library you can install the above components to the desired locations on your system. For a system-wide install you may place the static or shared library into /usr/local/lib/, copy the CIO locator file to the place you specified in config.mk etc. You may also want to copy the header files in include/ to e.g. /usr/local/include so you can compile your application against SuperNOVAS easily on your system.

Building your application with SuperNOVAS

Provided you have installed the SuperNOVAS headers into a standard location (such as /usr/include or /usr/local/include) and the static or shared library into usr/lib (or /usr/local/lib or similar), you can build your application against it very easily. For example, to build myastroapp.c against SuperNOVAS, you might have a Makefile with contents like:

  myastroapp: myastroapp.c 
    $(CC) -o $@ $(CFLAGS) $^ -lm -lsupernovas

If you have a legacy NOVAS C 3.1 application, it is possible that the compilation will give you errors due to missing includes for stdio.h, stdlib.h, ctype.h or string.h. This is because these were explicitly included in novas.h in NOVAS C 3.1, but not in SuperNOVAS (at least not by default), as a matter of best practice. If this is a problem for you can 'fix' it in one of two ways: (1) Add the missing #include directives to your application source explicitly, or if that's not an option for you, then (2) set the -DCOMPAT=1 compiler flag when compiling your application:

  myastroapp: myastroapp.c 
    $(CC) -o $@ $(CFLAGS) -DCOMPAT=1 $^ -lm -lsupernovas

If your application uses the legacy solsys1.c or solsys2.c implementations for solarsystem() calls you may additionally specify the appropriate optional shared library also:

  myastroapp: myastroapp.c 
    $(CC) -o $@ $(CFLAGS) $^ -lm -lsupernovas -lsolsys1

To use your own solarsystem() implementation for ephemeris(), you will want to build the library with DEFAULT_SOLSYS not set (or else set to 0) in config.mk (see section above), and your applications Makefile may contain something like:

  myastroapp: myastroapp.c my_solsys.c 
    $(CC) -o $@ $(CFLAGS) $^ -lm -lsupernovas

The same principle applies to using your specific readeph() implementation (only with DEFAULT_READEPH being unset in config.mk).


Example usage

Note on alternative methodologies

The IAU 2000 and 2006 resolutions have completely overhauled the system of astronomical coordinate transformations to enable higher precision astrometry. (Super)NOVAS supports coordinate calculations both in the old (pre IAU 2000) ways, and in the new IAU standard method. Here is an overview of how the old and new methods define some of the terms differently:

Concept Old standard New IAU standard
Catalog coordinate system FK4, FK5, HIP... International Celestial Reference System (ICRS)
Dynamical system True of Date (TOD) Celestial Intermediate Reference System (CIRS)
Dynamical R.A. origin equinox of date Celestial Intermediate Origin (CIO)
Precession, nutation, bias separate, no tidal terms IAU 2006 precession/nutation model
Celestial Pole offsets dψ, dε dx, dy
Earth rotation measure Greenwich Sidereal Time (GST) Earth Rotation Angle (ERA)
Fixed Earth System WGS84 International Terrestrial Reference System (ITRS)

See the various enums and constants defined in novas.h, as well as the descriptions on the various NOVAS routines on how they are appropriate for the old and new methodologies respectively.

In NOVAS, the barycentric BCRS and the geocentric GCRS systems are effectively synonymous to ICRS. The origin for positions and for velocities, in any reference system, is determined by the observer location in the vicinity of Earth (at the geocenter, on the surface, or in Earth orbit).

SuperNOVAS v1.1 has introduced a new, more intuitive, more elegant, and more efficient approach for calculating astrometric positions of celestial objects. The guide below is geared towards this new method. However, the original NOVAS C approach remains viable also (albeit often less efficient). You may find an equivalent example usage showcasing the original NOVAS method in LEGACY.md.

Calculating positions for a sidereal source

A sidereal source may be anything beyond the solar-system with 'fixed' catalog coordinates. It may be a star, or a galactic molecular cloud, or a distant quasar.

Specify the object of interest

First, you must provide the coordinates (which may include proper motion and parallax). Let's assume we pick a star for which we have B1950 (i.e. FK4) coordinates:

 cat_entry star; // Structure to contain information on sidereal source 

 // Let's assume we have B1950 (FK4) coordinates...
 // 16h26m20.1918s, -26d19m23.138s (B1950), proper motion -12.11, -23.30 mas/year, 
 // parallax 5.89 mas, radial velocity -3.4 km/s.
 make_cat_entry("Antares", "FK4", 1, 16.43894213, -26.323094, -12.11, -23.30, 5.89, -3.4, &star);

We must convert these coordinates to the now standard ICRS system for calculations in SuperNOVAS, first by calculating equivalent J2000 coordinates, by applying the proper motion and the appropriate precession. Then, we apply a small adjustment to convert from J2000 to ICRS coordinates.

 // First change the catalog coordinates (in place) to the J2000 (FK5) system... 
 transform_cat(CHANGE_EPOCH, NOVAS_JD_B1950, &star, NOVAS_JD_J2000, "FK5", &star);

 // Then convert J2000 coordinates to ICRS (also in place). Here the dates don't matter...
 transform_cat(CHANGE_J2000_TO_ICRS, 0.0, &star, 0.0, "ICRS", &star);

(Naturally, you can skip the transformation steps above if you have defined your source in ICRS coordinates from the start.) Once the catalog entry is defined in ICRS, you can proceed wrapping it in a generic source structure (which handles both catalog and ephemeris sources).

 object source;   // Common structure for a sidereal or an ephemeris source

 // Wrap it in a generic source data structure
 make_cat_object(&star, &source);

Specify the observer location

Next, we define the location where we observe from. Here we can (but don't have to) specify local weather parameters (temperature and pressure) also for refraction correction later (in this example, we'll skip the weather):

 observer obs;    // Structure to contain observer location 

 // Specify the location we are observing from
 // 50.7374 deg N, 7.0982 deg E, 60m elevation
 make_observer_on_surface(50.7374, 7.0982, 60.0, 0.0, 0.0, &obs);

Similarly, you can also specify observers in Earth orbit, in Sun orbit, at the geocenter, or at the Solar-system barycenter.

Specify the time of observation

Next, we set the time of observation. For a ground-based observer, you will need to provide SuperNOVAS with the UT1 - UTC time difference (a.k.a. DUT1), and the current leap seconds. Let's assume 37 leap seconds, and DUT1 = 0.114, then we can set the time of observation, for example, using the current UNIX time:

 novas_timespec obs_time;       // Structure that will define astrometric time
 struct timespec unix_time;     // Standard precision UNIX time structure

 // Get the current system time, with up to nanosecond resolution...
 clock_gettime(CLOCK_REALTIME, &unix_time);

 // Set the time of observation to the precise UTC-based UNIX time
 novas_set_unix_time(unix_time.tv_sec, unix_time.tv_nsec, 37, 0.114, &obs_time);

Alternatively, you may set the time as a Julian date in the time measure of choice (UTC, UT1, TT, TDB, GPS, TAI, TCG, or TCB):

 double jd_tai = ...     // TAI-based Julian Date 

 novas_set_time(NOVAS_TAI, jd_tai, leap_seconds, dut1, &obs_time);

or, for the best precision we may do the same with an integer / fractional split:

 long ijd_tai = ...     // Integer part of the TAI-based Julian Date
 double fjd_tai = ...   // Fractional part of the TAI-based Julian Date 

 novas_set_split_time(NOVAS_TAI, ijd_tai, fjd_tai, 37, 0.114, &obs_time);

Set up the observing frame

Next, we set up an observing frame, which is defined for a unique combination of the observer location and the time of observation:

 novas_frame obs_frame;  // Structure that will define the observing frame
 double dx = ...         // [mas] Earth polar offset x, e.g. from IERS Bulletin A.
 double dy = ...         // [mas] Earth polar offset y, from same source as above.

 // Initialize the observing frame with the given observing parameters
 novas_make_frame(NOVAS_REDUCED_ACCURACY, &obs, &obs_time, dx, dy, &obs_frame);

Here dx and dy are small diurnal (sub-arcsec level) corrections to Earth orientation, which are published in the IERS Bulletins. They are needed when converting positions from the celestial CIRS frame to the Earth-fixed ITRS frame. You may ignore these and set zeroes if sub-arcsecond precision is not required.

The advantage of using the observing frame, is that it enables very fast position calculations for multiple objects in that frame. So, if you need to calculate positions for thousands of sources for the same observer and time, it will be significantly faster than using the low-level NOVAS C routines instead. You can create derivative frames for different observer locations, if need be, via novas_change_observer().

Note that without a proper ephemeris provider for the major planets, you are invariably restricted to working with NOVAS_REDUCED_ACCURACY frames, providing milliarcsecond precision only. To create NOVAS_FULL_ACCURACY frames, with sub-μas precision, you will you will need a high-precision ephemeris provider for the major planets (beyond the low-precision Earth and Sun calculator included by default), to account for gravitational bending around massive planets. Without it, μas accuracy cannot be ensured, in general. Therefore, attempting to construct high-accuracy frames without an appropriate high-precision ephemeris provider will result in an error from the requisite ephemeris() call.

Calculate an apparent place on sky

Now we can calculate the apparent R.A. and declination for our source, which includes proper motion (for sidereal sources) or light-time correction (for Solar-system bodies), and also aberration corrections for the moving observer and gravitational deflection around the major Solar System bodies. You can calculate an apparent location in the coordinate system of choice (ICRS/GCRS, CIRS, J2000, MOD, or TOD):

  sky_pos apparent;    // Structure containing the precise observed position

  novas_sky_pos(&source, &obs_frame, NOVAS_CIRS, &apparent);

Apart from providing precise apparent R.A. and declination coordinates, the sky_pos structure also provides the x,y,z unit vector pointing in the observed direction of the source (in the designated coordinate system). We also get radial velocity (for spectroscopy), and apparent distance for Solar-system bodies (e.g. for apparent-to-physical size conversion).

Note, that if you want geometric positions (and/or velocities) instead, without aberration and gravitational deflection, you might use novas_geom_posvel() instead. And regardless, which function you use you can always easily and efficiently change the coordinate system in which your results are expressed by creating an appropriate transform via novas_make_transform() and then using novas_transform_vector() or novas_transform_skypos().

Calculate azimuth and elevation angles at the observing location

If your ultimate goal is to calculate the azimuth and elevation angles of the source at the specified observing location, you can proceed from the sky_pos data you obtained above (in whichever coordinate system!) as:

 double az, el;   // [deg] local azimuth and elevation angles to populate

 // Convert the apparent position in CIRS on sky to horizontal coordinates
 novas_app_to_hor(&obs_frame, NOVAS_CIRS, apparent.ra, apparent.dec, novas_standard_refraction, &az, &el);

Above we converted the apparent coordinates, assuming they were calculated in CIRS, to refracted azimuth and elevation coordinates at the observing location, using the novas_standard_refraction() function to provide a suitable refraction correction. We could have used novas_optical_refraction() instead to use the weather data embedded in the frame's observer structure, or some user-defined refraction model, or else NULL to calculate unrefracted elevation angles.

Calculating positions for a Solar-system source

Solar-system sources work similarly to the above with a few important differences.

First, You will have to provide one or more functions to obtain the barycentric ICRS positions for your Solar-system source(s) of interest for the specific Barycentric Dynamical Time (TDB) of observation. See section on integrating External Solar-system ephemeris data or services with SuperNOVAS. You can specify the functions that will handle the respective ephemeris data at runtime before making the NOVAS calls that need them, e.g.:

 // Set the function to use for regular precision planet position calculations
 set_planet_provider(my_planet_function);

 // Set the function for high-precision planet position calculations
 set_planet_provider_hp(my_very_precise_planet_function);

 // Set the function to use for calculating all other solar-system bodies
 set_ephem_provider(my_ephemeris_provider_function);

Instead of make_cat_object() you define your source as an object with an name or ID number that is used by the ephemeris service you provided. For major planets you might want to use make_planet(), if they use a novas_planet_provider function to access ephemeris data with their NOVAS IDs, or else make_ephem_object() for more generic ephemeris handling via a user-provided novas_ephem_provider. E.g.:

 object mars, ceres; // Hold data on solar-system bodies.

 // Mars will be handled by the planet provider function
 make_planet(NOVAS_MARS, &mars);

 // Ceres will be handled by the generic ephemeris provider function, which let's say 
 // uses the NAIF ID of 2000001
 make_ephem_object("Ceres", 2000001, &ceres);

Other than that, it's the same spiel as before, e.g.:

 int status = novas_sky_pos(&mars, &obs_frame, NOVAS_TOD, &apparent);
 if(status) {
   // Oops, something went wrong...
   ...
 }

Reduced accuracy shortcuts

When one does not need positions at the microarcsecond level, some shortcuts can be made to the recipe above:

Performance considerations

If accuracy below the milliarcsecond level is not required NOVAS_REDUCED_ACCURACY mode offers faster calculations, in general.

Multi-threaded calculations

Some of the calculations involved can be expensive from a computational perspective. For the most typical use case however, NOVAS (and SuperNOVAS) has a trick up its sleeve: it caches the last result of intensive calculations so they may be re-used if the call is made with the same environmental parameters again (such as JD time and accuracy).

A direct consequence of the caching of results in NOVAS is that calculations are generally not thread-safe as implemented by the original NOVAS C 3.1 library. One thread may be in the process of returning cached values for one set of input parameters while, at the same time, another thread is saving cached values for a different set of parameters. Thus, when running calculations in more than one thread, the results returned may at times be incorrect, or more precisely they may not correspond to the requested input parameters.

While you should never call NOVAS C from multiple threads simultaneously, SuperNOVAS caches the results in thread local variables (provided your compiler supports it), and is therefore safe to use in multi-threaded applications. Just make sure that you:


Notes on precision

Many of the (Super)NOVAS functions take an accuracy argument, which determines to what precision quantities are calculated. The argument can have one of two values, which correspond to typical precisions around:

enum novas_accuracy value Typical precision
NOVAS_REDUCED_ACCURACY ~ 1 milli-arcsecond (mas)
NOVAS_FULL_ACCURACY below 1 micro-arcsecond (μas)

Note, that some functions will not support full accuracy calculations, unless you have provided a high-precision ephemeris provider for the major planets (and any Solar-system bodies of interest), which does not come with SuperNOVAS out of the box. In the absense of a suitable high-precision ephemeris provider, some functions might return an error if called with NOVAS_FULL_ACCURACY.

Prerequisites to precise results

The SuperNOVAS library is in principle capable of calculating positions to sub-microarcsecond, and velocities to mm/s, precision for all types of celestial sources. However, there are certain prerequisites and practical considerations before that level of accuracy is reached.

  1. IAU 2000/2006 conventions: High precision calculations will generally require that you use SuperNOVAS with the new IAU standard quantities and methods. The old ways were simply not suited for precision much below the milliarcsecond level.

  2. Gravitational bending: Calculations much below the milliarcsecond level will require to account for gravitational bending around massive Solar-system bodies, and hence will require you to provide a high-precision ephemeris provider for the major planets. Without it, there is no guarantee of achieving the desired μas-level precision in general, especially when observing near massive planets (e.g. observing Jupiter's or Saturn's moons, near transit). Therefore some functions will return with an error, if used with NOVAS_FULL_ACCURACY in the absense of a suitable high-precision planetary ephemeris provider.

  3. Solar-system sources: Precise calculations for Solar-system sources requires precise ephemeris data for both the target object as well as for Earth, and the Sun. For the highest precision calculations you also need positions for all major planets to calculate gravitational deflection precisely. By default SuperNOVAS can only provide approximate positions for the Earth and Sun (see earth_sun_calc() in solsys3.c), but certainly not at the sub-microarcsecond level, and not for other solar-system sources. You will need to provide a way to interface SuperNOVAS with a suitable ephemeris source (such as the CSPICE toolkit from JPL or CALCEPH) if you want to use it to obtain precise positions for Solar-system bodies. See the section further below for more information how you can do that.

  4. Earth's polar motion: Calculating precise positions for any Earth-based observations requires precise knowledge of Earth orientation at the time of observation. The pole is subject to predictable precession and nutation, but also small irregular variations in the orientation of the rotational axis and the rotation period (a.k.a polar wobble). The IERS Bulletins provide up-to-date measurements, historical data, and near-term projections for the polar offsets and the UT1-UTC (DUT1) time difference and leap-seconds (UTC-TAI). In SuperNOVAS you can use cel_pole() and get_ut1_to_tt() functions to apply / use the published values from these to improve the astrometric precision of Earth-orientation based coordinate calculations. Without setting and using the actual polar offset values for the time of observation, positions for Earth-based observations will be accurate at the tenths of arcsecond level only.

    1. Refraction: Ground based observations are also subject to atmospheric refraction. SuperNOVAS offers the option to include approximate optical refraction corrections either for a standard atmosphere or more precisely using the weather parameters defined in the on_surface data structure that specifies the observer locations. Note, that refraction at radio wavelengths is notably different from the included optical model, and a standard radio refraction model is included as of version 1.1. In any case you may want to skip the refraction corrections offered in this library, and instead implement your own as appropriate (or not at all).

SuperNOVAS specific features

Newly added functionality

Added in v1.1

Added in v1.2

Refinements to the NOVAS C API


Incorporating Solar-system ephemeris data or services

If you want to use SuperNOVAS to calculate positions for a range of Solar-system objects, and/or to do it with sufficient precision, you will have to interface it to a suitable provider of ephemeris data, such as JPL Horizons or the Minor Planet Center. Given the NOVAS C heritage, and some added SuperNOVAS flexibility in this area, you have several options on doing that. These are listed from the most practical (and preferred) to the least so (the old ways).

NASA/JPL provides generic ephemerides for the major planets, satellites thereof, the 300 largest asteroids, the Lagrange points, and some Earth orbiting stations. For example, DE440 covers the major planets, and the Sun, Moon, and the Solar-System Barycenter (SSB) for times between 1550 AD and 2650 AD. Or, you can use the JPL HORIZONS system to generate custom ephemeris data for pretty much all known solar systems bodies, down to the tiniest rocks.

Optional support for CALCEPH integration

The CALCEPH library provides an easy-to-use access to JPL and INPOP ephemeris files from C/C++. As of version 1.2, we provide optional support for interfacing the CALCEPH C library with SuperNOVAS for handling Solar-system objects.

Prior to building SuperNOVAS simply set CALCEPH_SUPPORT to 1 in config.mk or in your environment. Depending on the build target, it will build libsolsys-calceph.so[.1] (target shared) or libsolsys-calceph.a (target static) libraries, which provide the novas_use_calceph() and novas_use_calceph_planets() functions.

Of course, you will need access to the CALCEPH C development files (C headers and unversioned libcalceph.so or .a library) for the build to succeed. Here is an example on how you'd use CALCEPH with SuperNOVAS in your application code:

  #include <calceph.h>

  // You can open a set of JPL/INPOP ephemeris files with CALCEPH...
  t_calcephbin *eph = calceph_open_array(...);

  // Then use them as your generic SuperNOVAS ephemeris provider
  int status = novas_use_calceph(eph);
  if(status < 0) {
    // Ooops something went wrong...
  }

  // -----------------------------------------------------------------------
  // Optionally you may use a separate ephemeris data for major planets
  // (or if planet ephemeris was included in 'eph' above, you don't have to) 
  t_calcephbin *pleph = calceph_open(...);
  int status = novas_use_calceph(pleph);
  if(status < 0) {
    // Ooops something went wrong...
  }

All modern JPL (SPK) ephemeris files should work with the solsys-calceph plugin. When linking your application, don't forget to add -lsolsys-calceph to your link flags. That's all there is to it.

Optional support for NAIF CSPICE toolkit integration

The NAIF CSPICE Toolkit is the canonical standard library for JPL ephemeris files from C/C++. As of version 1.2, we provide optional support for interfacing CSPICE with SuperNOVAS for handling Solar-system objects.

Prior to building SuperNOVAS simply set CSPICE_SUPPORT to 1 in config.mk or in your environment. Depending on the build target, it will build libsolsys-cspice.so[.1] (target shared) or libsolsys-cspice.a (target static) libraries, which provide the novas_use_cspice(), novas_use_cspice_planets(), and novas_use_cspice_ephem() functions to enable CSPICE for providing data for all Solar-system sources, or for major planets only, or for other bodies only, respectively.

Of course, you will need access to the CSPICE development files (C headers, installed under a cspice/ directory of an header search location, and the unversioned libcspice.so or .a library) for the build to succeed. You may want to check out the Smithsonian/cspice-sharedlib GitHub repository to help you build CSPICE with shared libraries and dynamically linked tools.

Here is an example on how you might use CSPICE with SuperNOVAS in your application code:

  // You can load the desired kernels for CSPICE, using the CSPICE API.
  // E.g. load DE440s and the Mars satellites from /data/ephem:
  furnsh_c("/data/ephem/de440s.bsp");
  furnsh_c("/data/ephem/mar097.bsp");
  ...

  // check for CSPICE errors
  if(return_c()) {
    // oops, one of the kernels must not have loaded...
    ...
  }

  // Then use CSPICE as your SuperNOVAS ephemeris provider
  novas_use_cspice();

All JPL ephemeris data will work with the solsys-cspice plugin. When linking your application, don't forget to add -lsolsys-cspice to your link flags. That's all there is to it.

Universal ephemeris data / service integration

Possibly the most universal way to integrate ephemeris data with SuperNOVAS is to write your own novas_ephem_provider, e.g.:

 int my_ephem_reader(const char *name, long id, double jd_tdb_high, double jd_tdb_low, 
                     enum novas_origin *origin, double *pos, double *vel) {
   // Your custom ephemeris reader implementation here
   ...
 }

which takes an object ID number (such as a NAIF) an object name, and a split TDB date (for precision) as it inputs, and returns the type of origin with corresponding ICRS position and velocity vectors in the supplied pointer locations. The function can use either the ID number or the name to identify the object or file (whatever is the most appropriate for the implementation and for the supplied parameters). The positions and velocities may be returned either relative to the SSB or relative to the heliocenter, and accordingly, your function should set the value pointed at by origin to NOVAS_BARYCENTER or NOVAS_HELIOCENTER accordingly. Positions and velocities are rectangular ICRS x,y,z vectors in units of AU and AU/day respectively.

This way you can easily integrate current ephemeris data, e.g. for the Minor Planet Center (MPC), or whatever other ephemeris service you prefer.

Once you have your adapter function, you can set it as your ephemeris service via set_ephem_provider():

 set_ephem_provider(my_ephem_reader);

By default, your custom my_ephem_reader function will be used for 'minor planets' only (i.e. anything other than the major planets, the Sun, Moon, and the Solar System Barycenter). But, you can use the same function for the mentioned 'major planets' also via:

 set_planet_provider(planet_ephem_provider);
 set_planet_provider_hp(planet_ephem_provider_hp);

provided you compiled SuperNOVAS with BUILTIN_SOLSYS_EPHEM = 1 (in config.mk), or else you link your code against solsys-ephem.c explicitly. Easy-peasy.

Legacy support for (older) JPL major planet ephemerides

If you only need support for major planets, you may be able to use one of the modules included in the SuperNOVAS distribution. The modules solsys1.c and solsys2.c provide built-in support to older JPL ephemerides (DE200 to DE421), either via the eph_manager interface of solsys1.c or via the FORTRAN pleph interface with solsys2.c.

Planets via eph_manager

To use the eph_manager interface for planet 1997 JPL planet ephemeris (DE200 through DE421), you must either build SuperNOVAS with BUILTIN_SOLSYS1 = 1 in config.mk, or else link your application with solsys1.c and eph_manager.c from SuperNOVAS explicitly. If you want eph_manager to be your default ephemeris provider (the old way) you might also want to set DEFAULT_SOLSYS = 1 in config.mk. Otherwise, your application should set eph_manager as your planetary ephemeris provider at runtime via:

 set_planet_provider(planet_eph_manager);
 set_planet_provider_hp(planet_eph_manager_hp);

Either way, before you can use the ephemeris, you must also open the relevant ephemeris data file with ephem_open():

 int de_number;          // The DE number, e.g. 405 for DE405
 double from_jd, to_jd;  // [day] Julian date range of the ephemeris data

 ephem_open("path-to/de405.bsp", &from_jd, &to_jd, &de_number);

And, when you are done using the ephemeris file, you should close it with

 ephem_close();

Note, that at any given time eph_manager can have only one ephemeris data file opened. You cannot use it to retrieve data from multiple ephemeris input files at the same time. (But you can with the CSPICE toolkit, which you can integrate as discussed further above!)

That's all, except the warning that this method will not work with newer JPL ephemeris data, beyond DE421.

Planets via JPL's pleph FORTRAN interface

To interface eith the JPL PLEPH library (FORTRAN) for planet ephemerides, you must either build SuperNOVAS with BUILTIN_SOLSYS2 = 1 in config.mk, or else link your application with solsys2.c and your appropriately modified jplint.f (from the examples sub-directory) explicitly, together with the JPL PLEPH library. If you want this to be your default ephemeris provider (the old way) you might also want to set DEFAULT_SOLSYS = 2 in config.mk. Otherwise, your application should set your planetary ephemeris provider at runtime via:

 set_planet_provider(planet_jplint);
 set_planet_provider_hp(planet_jplint_hp);

Integrating JPL ephemeris data this way can be arduous. You will need to compile and link FORTRAN with C (not the end of the world), but you may also have to modify jplint.f (providing the intermediate FORTRAN jplint_() / jplihp_() interfaces to pleph_()) to work with the version of pleph.f that you will be using. Unless you already have code that relies on this method, you are probably better off choosing one of the other ways for integrating planetary ephemeris data with SuperNOVAS.

Legacy linking of custom ephemeris functions

Finally, if none of the above is appealing, and you are fond of the old ways, you may compile SuperNOVAS with the DEFAULT_SOLSYS option disabled (commented, removed, or else set to 0), and then link your own implementation of solarsystem() and solarsystem_hp() calls with your application.

For Solar-system objects other than the major planets, you may also provide your own readeph() implementation. (In this case you will want to set DEFAULT_READEPH in config.mk to specify your source code for that function before building the SuperNOVAS library, or else disable that option entirely (e.g. by commenting or removing it), and link your application explicitly with your readeph() implementation.

The downside of this approach is that your SuperNOVAS library will not be usable without invariably providing a solarsystem() / solarsystem_hp() and/or readeph() implementations for every application that you will want to use SuperNOVAS with. This is why the runtime configuration of the ephemeris provider functions is the best and most generic way to add your preferred implementations while also providing some minimum default implementations for other users of the library, who may not need your ephemeris service, or have no need for planet data beyond the approximate positions for the Earth and Sun.


Runtime debug support

You can enable or disable debugging output to stderr with novas_debug(enum novas_debug_mode), where the argument is one of the defined constants from novas.h:

novas_debug_mode value Description
NOVAS_DEBUG_OFF No debugging output (default)
NOVAS_DEBUG_ON Prints error messages and traces to stderr
NOVAS_DEBUG_EXTRA Same as above but with stricter error checking

The main difference between NOVAS_DEBUG_ON and NOVAS_DEBUG_EXTRA is that the latter will treat minor issues as errors also, while the former may ignore them. For example, place() will return normally by default if it cannot calculate gravitational bending around massive planets in full accuracy mode. It is unlikely that this omission would significantly alter the result in most cases, except for some very specific ones when observing in a direction close to a major planet. Thus, with NOVAS_DEBUG_ON, place() go about as usual even if the Jupiter's position is not known. However, NOVAS_DEBUG_EXTRA will not give it a free pass, and will make place() return an error (and print the trace) if it cannot properly account for gravitational bending around the major planets as it is expected to.


Release schedule

A predictable release schedule and process can help manage expectations and reduce stress on adopters and developers alike.

Releases of the library shall follow a quarterly release schedule. You may expect upcoming releases to be published around February 1, May 1, August 1, and/or November 1 each year, on an as-needed basis. That means that if there are outstanding bugs, or new pull requests (PRs), you may expect a release that addresses these in the upcoming quarter. The dates are placeholders only, with no guarantee that a new release will actually be available every quarter. If nothing of note comes up, a potential release date may pass without a release being published.

Feature releases (e.g. 1.x.0 version bumps) are provided at least 6 months apart, to reduce stress on adopters who may need/want to tweak their code to integrate these. Between feature releases, bug fix releases (without significant API changes) may be provided as needed to address issues. New features are generally reserved for the feature releases, although they may also be rolled out in bug-fix releases as long as they do not affect the existing API -- in line with the desire to keep bug-fix releases fully backwards compatible with their parent versions.

In the weeks and month(s) preceding releases one or more release candidates (e.g. 1.0.1-rc3) will be published temporarily on GitHub, under Releases, so that changes can be tested by adopters before the releases are finalized. Please use due diligence to test such release candidates with your code when they become available to avoid unexpected surprises when the finalized release is published. Release candidates are typically available for one week only before they are superseded either by another, or by the finalized release.


Copyright (C) 2024 Attila Kovács