diff --git a/CHANGELOG.md b/CHANGELOG.md index dff89881..f075d28b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), Changes expected for the next feature release, expected around 1 February 2025. -## Added +### Added - #57: New `novas_make_redshifted_object()` to simplify the creation of distant catalog sources that are characterized with a redshift measure rather than a radial velocity value. @@ -34,11 +34,12 @@ Changes expected for the next feature release, expected around 1 February 2025. - #86: NAIF CSPICE integration: `novas_use_cspice()`, `novas_use_cspice_planets()`, `novas_use_cspice_ephem()` to use the NAIF CSPICE library for all Solar-system sources, major planets only, or for other bodies only. - `NOVAS_EPHEM_OBJECTS` should use NAIF IDs with CSPICE (or else -1 for name-based lookup). These functions are - provided by the `libsolsys-cspice.so[.1]` and/or `.a` plugin libraries, which are built contingent on the - `CSPICE_SUPPORT` variable being set to 1 prior to the build. The build of the plugin module requires an accessible - installation of the CSPICE development files (C headers and unversioned static or shared libraries depending on the - needs of the build). + `NOVAS_EPHEM_OBJECTS` should use NAIF IDs with CSPICE (or else -1 for name-based lookup). + Also provides `novas_cspice_add_kernel()` and `novas_cspice_remove_kernel()` for convenience to manage the set of + active kernels (#89). These functions are provided by the `libsolsys-cspice.so[.1]` and/or `.a` plugin libraries, + which are built contingent on the `CSPICE_SUPPORT` variable being set to 1 prior to the build. The build of the + plugin module requires an accessible installation of the CSPICE development files (C headers and unversioned static + or shared libraries depending on the needs of the build). - #87: Added `novas_planet_for_name()` function to return the NOVAS planet ID for a given (case insensitive) name. diff --git a/README.md b/README.md index 36f728a9..54c43b6e 100644 --- a/README.md +++ b/README.md @@ -825,7 +825,8 @@ before that level of accuracy is reached. - NAIF CSPICE integration: `novas_use_cspice()`, `novas_use_cspice_planets()`, `novas_use_cspice_ephem()` to use the NAIF CSPICE library for all Solar-system sources, major planets only, or for other bodies only. - `NOVAS_EPHEM_OBJECTS` should use NAIF IDs with CSPICE (or else -1 for name-based lookup). + `NOVAS_EPHEM_OBJECTS` should use NAIF IDs with CSPICE (or else -1 for name-based lookup). Also provides + `novas_cspice_add_kernel()` and `novas_cspice_remove_kernel()`. - NAIF/NOVAS ID conversions for major planets (and Sun, Moon, SSB): `novas_to_naif_planet()`, `novas_to_dexxx_planet()`, and `naif_to_novas_planet()`. @@ -1000,18 +1001,21 @@ repository to help you build CSPICE with shared libraries and dynamically linked Here is an example on how you might use CSPICE with SuperNOVAS in your application code: ```c - // You can load the desired kernels for CSPICE, using the CSPICE API. + // You can load the desired kernels for CSPICE // E.g. load DE440s and the Mars satellites from /data/ephem: - furnsh_c("/data/ephem/de440s.bsp"); - furnsh_c("/data/ephem/mar097.bsp"); - ... + int status; - // check for CSPICE errors - if(return_c()) { - // oops, one of the kernels must not have loaded... + status = novas_cspice_add_kernel("/data/ephem/de440s.bsp"); + if(status < 0) { + // oops, the kernels must not have loaded... ... } + // Load additional kernels as needed... + status = novas_cspice_add_kernel("/data/ephem/mar097.bsp"); + ... + + // Then use CSPICE as your SuperNOVAS ephemeris provider novas_use_cspice(); ``` diff --git a/include/solarsystem.h b/include/solarsystem.h index da829e65..2a03de0d 100644 --- a/include/solarsystem.h +++ b/include/solarsystem.h @@ -350,7 +350,11 @@ int novas_use_cspice_ephem(); int novas_use_cspice_planets(); -#endif /* USE_CALCEPH */ +int novas_cspice_add_kernel(const char *filename); + +int novas_cspice_remove_kernel(const char *filename); + +#endif /* USE_CSPICE */ /// \cond PRIVATE diff --git a/src/solsys-calceph.c b/src/solsys-calceph.c index ae3444fa..b96f390f 100644 --- a/src/solsys-calceph.c +++ b/src/solsys-calceph.c @@ -202,8 +202,10 @@ static short planet_calceph_hp(const double jd_tdb[2], enum novas_planet body, e return novas_error(3, EAGAIN, fn, "calceph_compute() failure (NOVAS ID=%d)", body); for(i = 3; --i >= 0;) { - if(position) position[i] = pv[i] * NORM_POS; - if(velocity) velocity[i] = pv[3 + i] * NORM_VEL; + if(position) + position[i] = pv[i] * NORM_POS; + if(velocity) + velocity[i] = pv[3 + i] * NORM_VEL; } return 0; @@ -334,8 +336,10 @@ static int novas_calceph(const char *name, long id, double jd_tdb_high, double j return novas_error(3, EAGAIN, fn, "calceph_compute() failure (name='%s', NAIF=%ld)", name ? name : "", id); for(i = 3; --i >= 0;) { - if(pos) pos[i] = pv[i] * NORM_POS; - if(vel) vel[i] = pv[3 + i] * NORM_VEL; + if(pos) + pos[i] = pv[i] * NORM_POS; + if(vel) + vel[i] = pv[3 + i] * NORM_VEL; } return 0; diff --git a/src/solsys-cspice.c b/src/solsys-cspice.c index e1aa9ee3..1cb4d081 100644 --- a/src/solsys-cspice.c +++ b/src/solsys-cspice.c @@ -55,6 +55,131 @@ static int mutex_unlock() { return 0; } +/** + * Supresses CSPICE error output and disables exit on error behavior, so we can check and process + * CSPICE errors gracefully ourselves. + * + * REFERENCES: + *
    + *
  1. https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/erract_c.html
  2. + *
  3. https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/errprt_c.html
  4. + *
+ * + * @return 0 + */ +static int suppress_cspice_errors() { + erract_c("SET", 0, "RETURN"); // Do not exit in case of CSPICE errors. + errprt_c("SET", 0, "NONE"); // Suppress CSPICE error messages + return 0; +} + +/** + * Returns a short description of the CSPICE error in the supplied buffer, and resets the + * CSPICE error state. + * + * @param[out] msg the buffer in which to return the message. + * @param len (bytes) maximum length of the message to return including termination + * @return the CSPICE error code. + */ +static int get_cspice_error(char *msg, int len) { + int err = return_c(); + getmsg_c("SHORT", len, msg); + reset_c(); + return err; +} + +/** + * Adds a SPICE kernel to the currently managed open kernels. Subsequent ephemeris lookups through + * CSPICE will use the added kernel. It's simply a wrapper around the CSPICE `furnsh_c()` routine, + * with graceful error handling. You can of course add kernels using `furnsh_c()` directly to the + * same effect. + * + * REFERENCES: + *
    + *
  1. https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/furnsh_c.html
  2. + *
+ * + * @param filename The fully qualified path to the ephemeris kernel data (e.g. + * "/data/ephem/de440s.bsp") + * @return 0 if successful, or else -1 if there was an error (errno will be set to + * EINVAL). + * + * @sa novas_cspice_remove_kernel() + * + * @author Attila Kovacs + * @since 1.2 + */ +int novas_cspice_add_kernel(const char *filename) { + static const char *fn = "novas_cspice_add_kernel"; + + char msg[100]; + int err; + + if(!filename) + return novas_error(-1, EINVAL, fn, "input filename is NULL"); + if(!filename[0]) + return novas_error(-1, EINVAL, fn, "input filename is empty"); + + suppress_cspice_errors(); + + prop_error(fn, mutex_lock(), 0); + reset_c(); + furnsh_c(filename); + err = get_cspice_error(msg, sizeof(msg)); + mutex_unlock(); + + if(err) + return novas_error(-1, EINVAL, fn, "furnsh_c(%s): %s", filename, msg); + + return 0; +} + +/** + * Removes a SPICE kernel from the currently managed open kernels. Subsequent ephemeris lookups + * through CSPICE will not use the removed kernel data. It's simply a wrapper around the CSPICE + * `unload_c()` routine, with graceful error handling. You can of course remove kernels using + * `unload_c()` directly to the same effect. + * + * REFERENCES: + *
    + *
  1. https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/unload_c.html
  2. + *
+ * + * @param filename The fully qualified path to the ephemeris kernel data (e.g. + * "/data/ephem/de440s.bsp") + * @return 0 if successful, or else -1 if there was an error (errno will be set to + * EINVAL). + * + * @sa novas_cspice_add_kernel() + * + * @author Attila Kovacs + * @since 1.2 + */ +int novas_cspice_remove_kernel(const char *filename) { + static const char *fn = "novas_cspice_remove_kernel"; + + char msg[100]; + int err; + + if(!filename) + return novas_error(-1, EINVAL, fn, "input filename is NULL"); + if(!filename[0]) + return novas_error(-1, EINVAL, fn, "input filename is empty"); + + suppress_cspice_errors(); + + prop_error(fn, mutex_lock(), 0); + reset_c(); + unload_c(filename); + err = get_cspice_error(msg, sizeof(msg)); + mutex_unlock(); + + if(err) + return novas_error(-1, EINVAL, fn, "unload_c(%s): %s", filename, msg); + + return 0; +} + /** * Provides an interface between the NAIF CSPICE C library and NOVAS-C for regular (reduced) * precision applications. The user must set the cspice ephemeris binary data to use using the @@ -71,6 +196,7 @@ static int mutex_unlock() { * REFERENCES: *
    *
  1. NAIF CSPICE: https://naif.jpl.nasa.gov/naif/toolkit.html
  2. + *
  3. https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/spkez_c.html
  4. *
  5. Kaplan, G. H. "NOVAS: Naval Observatory Vector Astrometry * Subroutines"; USNO internal document dated 20 Oct 1988; * revised 15 Mar 1990.
  6. @@ -102,6 +228,7 @@ static short planet_cspice_hp(const double jd_tdb[2], enum novas_planet body, en double *velocity) { static const char *fn = "planet_cspice_hp"; + char msg[100]; SpiceDouble pv[6]; SpiceDouble lt; SpiceInt target, center; @@ -112,7 +239,8 @@ static short planet_cspice_hp(const double jd_tdb[2], enum novas_planet body, en return novas_error(-1, EINVAL, fn, "jd_tdb input time array is NULL."); target = novas_to_naif_planet(body); - if(target < 0) return novas_trace(fn, 1, 0); + if(target < 0) + return novas_trace(fn, 1, 0); switch(origin) { case NOVAS_BARYCENTER: @@ -135,27 +263,27 @@ static short planet_cspice_hp(const double jd_tdb[2], enum novas_planet body, en // "J2000" and "ICRF" are treated the same, with "J2000" being the compatibility label. reset_c(); spkez_c(target, tdb2000, "J2000", "NONE", center, pv, <); - err = return_c(); - reset_c(); + err = get_cspice_error(msg, sizeof(msg)); if(err) { SpiceInt alt = novas_to_dexxx_planet(body); if(alt != target) { // Try with DExxx ID (barycenter vs planet center) spkez_c(alt, tdb2000, "J2000", "NONE", center, pv, <); - err = return_c(); - reset_c(); + err = get_cspice_error(msg, sizeof(msg)); } } mutex_unlock(sem); if(err) - return novas_error(3, EAGAIN, fn, "spkez_c() error (NOVAS ID=%d)", body); + return novas_error(3, EAGAIN, fn, "spkez_c(NOVAS ID=%d, JD=%.1f): %s", body, (jd_tdb[0] + jd_tdb[1]), msg); for(i = 3; --i >= 0;) { - if(position) position[i] = pv[i] * NORM_POS; - if(velocity) velocity[i] = pv[3 + i] * NORM_VEL; + if(position) + position[i] = pv[i] * NORM_POS; + if(velocity) + velocity[i] = pv[3 + i] * NORM_VEL; } return 0; @@ -218,6 +346,11 @@ static short planet_cspice(double jd_tdb, enum novas_planet body, enum novas_ori * The call will use whatever ephemeris (SPK) files were loaded by the CSPICE library prior * to the call (see furnsh_c() function) * + * REFERENCES: + *
      + *
    1. https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/spkez_c.html
    2. + *
    + * * @param name The name of the solar-system body. It is important only if the 'id' is * -1. * @param id The NAIF ID number of the solar-system body for which the position in @@ -253,6 +386,7 @@ static short planet_cspice(double jd_tdb, enum novas_planet body, enum novas_ori static int novas_cspice(const char *name, long id, double jd_tdb_high, double jd_tdb_low, enum novas_origin *origin, double *pos, double *vel) { static const char *fn = "novas_cspice"; + char msg[100]; SpiceDouble pv[6]; SpiceDouble lt; SpiceInt target, center; @@ -276,14 +410,13 @@ static int novas_cspice(const char *name, long id, double jd_tdb_high, double jd reset_c(); bodn2c_c(spiceName, &spiceCode, &spiceFound); - err = return_c(); - reset_c(); + err = get_cspice_error(msg, sizeof(msg)); if(!spiceFound) return novas_error(1, EINVAL, fn, "CSPICE could not find a NAIF ID for '%s'", name); if(err) - return novas_error(1, EINVAL, fn, "CSPICE name lookup error for '%s'", name); + return novas_error(1, EINVAL, fn, "CSPICE name lookup error for '%s': %s", name, msg); id = spiceCode; } @@ -304,17 +437,19 @@ static int novas_cspice(const char *name, long id, double jd_tdb_high, double jd // "J2000" and "ICRF" are treated the same, with "J2000" being the compatibility label. reset_c(); spkez_c(target, tdb2000, "J2000", "NONE", center, pv, <); - err = return_c(); - reset_c(); + err = get_cspice_error(msg, sizeof(msg)); mutex_unlock(); if(err) - return novas_error(3, EAGAIN, fn, "spkez_c() failure (name='%s', NAIF=%ld)", name ? name : "", id); + return novas_error(3, EAGAIN, fn, "spkez_c(name='%s', NAIF=%ld, JD=%.1f): %s", + name ? name : "", id, (jd_tdb_high + jd_tdb_low), msg); for(i = 3; --i >= 0;) { - if(pos) pos[i] = pv[i] * NORM_POS; - if(vel) vel[i] = pv[3 + i] * NORM_VEL; + if(pos) + pos[i] = pv[i] * NORM_POS; + if(vel) + vel[i] = pv[3 + i] * NORM_VEL; } return 0; @@ -337,8 +472,7 @@ static int novas_cspice(const char *name, long id, double jd_tdb_high, double jd * @since 1.2 */ int novas_use_cspice_ephem() { - errprt_c("SET", 100, "NONE"); // Suppress CSPICE error messages - erract_c("SET", 0, "RETURN"); // Do not exit in case of CSPICE errors. + suppress_cspice_errors(); set_ephem_provider(novas_cspice); return 0; } @@ -362,8 +496,7 @@ int novas_use_cspice_ephem() { * @since 1.2 */ int novas_use_cspice_planets() { - errprt_c("SET", 100, "NONE"); // Suppress CSPICE error messages - erract_c("SET", 0, "RETURN"); // Do not exit in case of CSPICE errors. + suppress_cspice_errors(); set_planet_provider_hp(planet_cspice_hp); set_planet_provider(planet_cspice); return 0; diff --git a/test/src/test-cspice.c b/test/src/test-cspice.c index 4dfe2937..6cbeb2cf 100644 --- a/test/src/test-cspice.c +++ b/test/src/test-cspice.c @@ -172,18 +172,44 @@ static int test_errors() { return n; } +static int load_eph(const char *name) { + char filename[1024]; + sprintf(filename, "%s/%s", prefix, name); + return novas_cspice_add_kernel(filename); +} -static void load_eph(const char *name) { +static int unload_eph(const char *name) { char filename[1024]; sprintf(filename, "%s/%s", prefix, name); - reset_c(); - furnsh_c(filename); - if(return_c()) { - fprintf(stderr, "ERROR! furnsh_c() failed for %s\n", name); - exit(1); - } + return novas_cspice_remove_kernel(filename); +} + +static int test_remove_kernel() { + int n = 0; + + if(!is_ok("remove_kernel:planets", unload_eph(PLANET_EPH))) n++; + if(!is_ok("remove_kernel:mars", unload_eph(MARS_EPH))) n++; + + if(check("remove_kernel:null", -1, novas_cspice_remove_kernel(NULL))) n++; + if(check("remove_kernel:empty", -1, novas_cspice_remove_kernel(""))) n++; + + return n; +} + + + +static int init() { + int n = 0; + + if(!is_ok("init:planets", load_eph(PLANET_EPH))) n++; + if(!is_ok("init:mars", load_eph(MARS_EPH))) n++; + + if(check("init:add_kernel:null", -1, novas_cspice_add_kernel(NULL))) n++; + if(check("init:add_kernel:empty", -1, novas_cspice_add_kernel(""))) n++; + + return n; } int main(int argc, char *argv[]) { @@ -193,8 +219,7 @@ int main(int argc, char *argv[]) { prefix = strdup(argv[1]); - load_eph(PLANET_EPH); - load_eph(MARS_EPH); + if(init()) return 1; enable_earth_sun_hp(1); @@ -204,5 +229,7 @@ int main(int argc, char *argv[]) { novas_debug(NOVAS_DEBUG_OFF); if(test_errors()) n++; + if(test_remove_kernel()) n++; + return n; }