rust-spice 1.0.0

WOW! The complete NASA/NAIF Spice toolkit is actually usable on Rust.
docs.rs failed to build rust-spice-1.0.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: rust-spice-1.0.1

rust-spice

logo image

ci badge crate badge doc badge license badge pre-commit badge coverage doc badge coverage test badge

WOW! The complete NASA/NAIF Spice toolkit is actually usable on Rust

The Rust wrapper now covers 100% of CSPICE N0067: all 644 routines that can be called, every one of them tested.


Intro | Requirements | Usage | In action | Coverage | Multi-threaded usage | Roadmap | Contributors | License


Intro

SPICE is An Observation Geometry System for Space Science Missions. Visit their website.

Requirements

  1. Install CSPICE library for your platform.
  2. Set the environment variable CSPICE_DIR to your CSPICE installation folder (where CSPICE subfolders include and lib are located. You can do that in the Cargo configuration).
  3. In the cspice/lib folder you might need for Unix systems to rename the static library to match standards: cspice.a -> libcspice.a

On Linux and macOS, .github/install-cspice.sh does all three for you:

./.github/install-cspice.sh ~/cspice
export CSPICE_DIR=~/cspice

See other requirements at cspice-sys library which provides unsafe bindings to CSPICE.

Usage

Add the dependency rust-spice to your Cargo.toml:

[dependencies]
rust-spice = "*" # replace * by the latest version of the crate

cspice-sys library depends on Clang which might not be available to your system. In this case, you can use the feature noclang:

[dependencies]
rust-spice = {version = "*", default-features = false, features = ["noclang"] }

To enable the lock feature (see ## Multi-threaded usage).

[dependencies]
rust-spice = {version = "*", features = ["lock"] }

In action

A nice and idiomatic interface to Spice,

use spice;

spice::furnsh("/path/to/metakernel.tm");

let et = spice::str2et("2027-MAR-23 16:00:00");
let (position, light_time) = spice::spkpos("DIMORPHOS", et, "J2000", "NONE", "SUN");

// position -> 18.62640405424448, 21.054373008357004, -7.136291402940499
// light time -> 0.00009674257074746383

spice::kclear();

You can look for some inspirations in the tests. They are self contained: the kernels they need are generated in a temporary directory when the suite starts, so cargo test works on any machine with CSPICE installed.

Errors

CSPICE keeps its own error state, and out of the box a failing routine prints a report to the screen and then terminates the process. Ask it to return instead, and turn what it reports into a Rust Result:

use spice;

spice::errors::quiet();

spice::furnsh("/path/to/metakernel.tm");
if let Err(error) = spice::errors::check() {
    eprintln!("could not load the kernels: {error}");
}

Cells

A few routines report their results by filling a SPICE cell, a fixed capacity array the toolkit manages itself. spice::Cell owns its storage, so it is created and dropped like any other Rust value:

use spice;

spice::furnsh("/path/to/metakernel.tm");

// The convenience form allocates the cell for you.
let bodies = spice::dskobj("/path/to/shape.bds");
for id in bodies.iter() {
    println!("{}", spice::bodc2n(id).0);
}

// The raw form takes the cell you sized, exactly as CSPICE does.
let mut ids = spice::Cell::<i32>::new(64);
spice::raw::spkobj("/path/to/ephemeris.bsp", &mut ids);

spice::kclear();

Coverage

The whole toolkit is wrapped: all 644 routines of CSPICE N0067 that can be called. They cover ephemerides, orientation, shape models, frames, time and spacecraft clocks, coordinates, vector and matrix algebra, rotations and quaternions, planes and ellipses, two-body orbits, two-line elements, cells, sets and windows, the geometry finder, events kernels, the kernel pool and error handling. The documentation online indexes every one of them, and every one of them is called by a test.

The headers declare 649 *_c functions: four are private internals whose names begin with zz, and prefix_c is declared but never compiled into the library NAIF ships, so it cannot be called at all.

The unsafe cspice functions are still there for anything you would rather drive yourself. The example above would be,

use spice;
use std::ffi::CString;

unsafe {
    let kernel = CString::new("/path/to/metakernel.tm").unwrap().into_raw();
    spice::c::furnsh_c(kernel);

    let mut et = 0.0;
    let date = CString::new("2027-MAR-23 16:00:00").unwrap().into_raw();
    spice::c::str2et_c(date, &mut et);

    let target_c = CString::new("DIMORPHOS").unwrap().into_raw();
    let frame_c = CString::new("J2000").unwrap().into_raw();
    let abcorr_c = CString::new("NONE").unwrap().into_raw();
    let observer_c = CString::new("SUN").unwrap().into_raw();
    let mut light_time = 0.0;
    let mut position = [0.0, 0.0, 0.0];
    spice::c::spkpos_c(
        target_c,
        et,
        frame_c,
        abcorr_c,
        observer_c,
        &mut position[0],
        &mut light_time,
    );

    spice::c::kclear_c();
}

Multi-threaded usage

CSPICE itself contains massive amounts of shared mutable state and is thus not thread-safe - concurrent calls to any SPICE functions will almost always lead to crashes. To prevent this, if you need to call SPICE functions from multiple threads, this crate provides a thread-safe API with the lock feature. When enabled, the API is exposed in the form of associated functions on a guard singleton SpiceLock, which is !Sync + Send. You can then only share this singleton and thus the methods it provides between threads using a Mutex, preventing concurrent API usage.

The lock exposes the neat versions of functions where available, and the raw versions for the rest. For functions which have neither, you will have to use the unsafe (and unguarded) direct C bindings. Just make sure you have the lock before calling them.

# #[cfg(feature = "lock")]
# {
use spice::SpiceLock;

// `try_acquire` will return `Err` if a lock already exists
let sl = SpiceLock::try_acquire().unwrap();

// SPICE functions are now associated functions of the lock with a `&self` arg
sl.furnsh("/path/to/metakernel.tm");

let et = sl.str2et("2027-MAR-23 16:00:00");
let (position, light_time) = sl.spkpos("DIMORPHOS", et, "J2000", "NONE", "SUN");

sl.kclear();
# }

Roadmap

Done: the test suite builds the kernels it needs rather than shipping them, the whole API is wrapped, the procedural macros were rewritten, and Cell owns its storage.

Next: idiomatic forms for the routines that still ask the caller for a buffer size, and a guard that can be shared rather than moved.

Contributors

Hall of fame:

A huge thanks for their contributions!!

License

Licensed under the Apache License, Version 2.0.