Crate rsspice

Source
Expand description

Pure Rust port of the SPICE Toolkit for space geometry.

This implementation is fully memory-safe and thread-safe, and does not depend on any external C/FORTRAN libraries. It provides nearly the entire SPICELIB API. The code has been mechanically translated from the FORTRAN version of the SPICE Toolkit into Rust.

It is completely unofficial, unsupported, and not heavily tested (though it does pass the Toolkit’s regression tests so it’s probably not too bad). Use at your own risk.

Users of this library should not expect any support from NAIF. Use the GitHub project for any issues or queries.

§Usage example

This example demonstrates the general design:

use rsspice::*;

const TIMFMT: &str = "YYYY MON DD HR:MN:SC.###### (TDB)::TDB";
const MAXWIN: usize = 2 * 100;

// Find solar eclipses as seen from the center of the Earth.
fn main() -> Result<()> {
    let mut spice = SpiceContext::new();

    spice.furnsh("gfoclt_ex1.tm")?;

    let mut confine = Cell::with_capacity(2);
    let mut result = Cell::with_capacity(MAXWIN);

    let et0 = spice.str2et("2027 JAN 01 00:00:00 TDB")?;
    let et1 = spice.str2et("2029 JAN 01 00:00:00 TDB")?;

    spice.wninsd(et0, et1, &mut confine)?;

    spice.gfoclt(
        "ANY",
        "MOON", "ellipsoid", "IAU_MOON",
        "SUN", "ellipsoid", "IAU_SUN",
        "LT", "EARTH", 180.0, &confine,
        &mut result,
    )?;

    for i in 1..=spice.wncard(&result)? {
        let (left, right) = spice.wnfetd(&result, i)?;
        println!(
            "Interval {i}: {} - {}",
            spice.timout(left, TIMFMT)?,
            spice.timout(right, TIMFMT)?
        );
    }

    Ok(())
}

(See more examples and lessons.)

The SpiceContext object encapsulates all the SPICE state, such as loaded kernels. There is no process-wide global state, so you can run multiple SpiceContexts concurrently in separate threads.

There is a 1:1 mapping between functions in the FORTRAN API and in the Rust API, so you can refer to the extensive FORTRAN documentation and adapt its examples relatively easily. (The API docs and required reading are mirrored in rustdoc. Further tutorials and lessons are available from NAIF.)

Arguments are not exactly a 1:1 mapping – we try to turn them into more idiomatic Rust types. Output arguments are typically mapped onto return values. SPICE errors are mapped onto Rust’s error system, so they can be easily handled with Result and ?.

FORTRAN arrays are typically indexed from 1, not 0. Functions like wnfetd similarly start counting from 1; we do not attempt any automatic translation of indexes. (This differs from the CSPICE translation, and wrappers around CSPICE, which count from 0.)

There is a Cell type for SPICE cells (including windows and sets), as they are particularly awkward to handle with standard Rust types. But Cell only provides very basic functionality; this library is not attempting to provide a higher-level API than the original SPICELIB.

§Background

SPICE is “an observation geometry system for space science missions”, developed by NASA’s Navigation and Ancillary Information Facility (NAIF).

A large amount of geometric data about planets, moons, and spacecraft is publicly available as SPICE data, which can be processed using the SPICE Toolkit software and APIs. NAIF also provides thorough documentation of the system.

The SPICE Toolkit is originally developed in FORTRAN, with an official translation to C. Official and unofficial bindings for the C library are available in several other languages (including rust-spice). rsspice is an unofficial translation from FORTRAN to Rust, with a number of benefits and drawbacks:

  • Memory-safe: Rust’s bounds-checking ensures that many errors will be detected at runtime and will not result in data corruption.

  • Thread-safe: The FORTRAN and C implementations depend heavily on global state. rsspice moves that state into the SpiceContext object, allowing concurrency within a single process. (See a basic example using rayon to split work across threads.)

  • Portability: This should work on any platform that Rust supports, including WebAssembly (albeit with some complications around filesystem access).

  • Much less testing: rsspice includes a translation of the TSPICE regression tests, which are reasonably extensive but do not have full coverage of the whole API. There is a higher risk of bugs than in a wrapper around the well-tested FORTRAN/C implementations.

§API mapping details

The API is mechanically translated from the FORTRAN API, following a number of rules to make it closer to idiomatic Rust. Understanding these can help when reading the FORTRAN documentation.

§Return values

Arguments that are documented as “O” are turned into return values. If there are multiple outputs, the function will return a tuple. If one of the outputs is called FOUND, it will not be returned explicitly; instead the function will return an Option<_>.

There are some exceptions, typically array outputs where there is no well-defined maximum size that we can allocate automatically. In this case they are mapped onto &mut parameters, and you must initialise the array with an appropriate size before the call.

For returned errors, see Error.

§Strings

Input strings are &str, outputs are typically String, mixed input-output and some outputs are &mut str.

Since FORTRAN 77 does not have dynamic allocation, output strings are allocated at the maximum possible size, and FORTRAN will pad the output with trailing space characters. We trim the trailing spaces before returning a String. When using &mut str, you are responsible for allocating and trimming:

let mut outstr = " ".repeat(80);
spice.replwd("Hello world", 1, "Goodbye", &mut outstr);
assert_eq!(outstr.trim_ascii_end(), "Goodbye world");

For FORTRAN CHARACTER arrays, see CharVec.

FORTRAN 77 barely even understands ASCII, never mind UTF-8. Input strings are simply interpreted as a sequence of bytes. For outputs, the implementation will panic if it ends up producing a non-UTF-8 string; this should not happen unless you pass non-ASCII characters into the API (so don’t do that).

§Hidden arguments

Some functions (like GFDIST) have a WORK argument, for temporary data storage. In rsspice we dynamically allocate that storage space, so the WORK argument and the corresponding NW size are removed from the API.

§Omitted functions

A small number of functions are excluded from the API, because they are:

  • Private;
  • Deprecated/obsolete;
  • Documented as “DO NOT CALL THIS ROUTINE”, or return a BOGUSENTRY error;
  • Redundant with basic Rust functionality, particularly string manipulation.

These functions are still available in the raw API.

§Array arguments

3D vector arguments are typically represented as &[f64; 3]. You can conveniently use nalgebra for these:

use nalgebra as na;
let v = na::Vector3::new(1.0, 2.0, 3.0);
let r = na::Vector3::from(
    spice.vrotv(v.as_ref(), &[0.0, 0.0, 1.0], std::f64::consts::FRAC_PI_2));
assert_relative_eq!(r, na::Vector3::new(-2.0, 1.0, 3.0));

If you want to use an &[f64] slice (e.g. from a Vec), you can use try_into().unwrap() (which will panic if the slice is too small):

let v = [0.0, 1.0, 2.0, 3.0, 4.0];
let r = spice.vrotv(&v[1..4].try_into().unwrap(),
    &[0.0, 0.0, 1.0],
    std::f64::consts::FRAC_PI_2);
assert_relative_eq!(r.as_slice(), [-2.0, 1.0, 3.0].as_slice());

Matrices are represented as &[[f64; 3]; 3] in column-major order (i.e. the first column is stored in memory first, then the second column, etc). This is compatible with nalgebra::Matrix3:

use nalgebra as na;
let m = na::Matrix3::new(
    0.0, -1.0, 0.0,
    0.5,  0.0, 0.0,
    0.0,  0.0, 1.0);
let mout = na::Matrix3::from(spice.invert(m.as_ref()));
assert_relative_eq!(
    mout,
    na::Matrix3::new(
         0.0, 2.0, 0.0,
        -1.0, 0.0, 0.0,
         0.0, 0.0, 1.0)
);

When reading any FORTRAN example code, note that multidimensional arrays in Rust have indexes in the opposite order to FORTRAN: M(I, J) corresponds to m[j][i]. But nalgebra uses FORTRAN-like indexing: m[(i, j)].

§Raw API

An alternative version of the API is available in the raw module. This is a closer match to the FORTRAN API, without the special handling of return values, cells, etc. Each raw function reproduces the original FORTRAN API documentation, detailing every input/output argument. You probably shouldn’t need to use this API directly, but the documentation is very helpful.

§License

Much of this crate is derived from the SPICE Toolkit, which is made freely available by NAIF but does not have a clear licensing situation. See LICENSE.md for details.

The non-Toolkit-derived code in this crate is licensed under either of

at your option.

Modules§

raw
Lower-level SPICELIB API.
required_reading
Collection of reference documents describing the various SPICE subsystems.

Structs§

Cell
Dynamic arrays of i32 or f64.
CharCell
Dynamic arrays of strings.
CharVec
Vector of equal-length strings.
SpiceContext
SPICELIB API.

Enums§

Error
SPICELIB errors.

Type Aliases§

Result