Skip to main content

Calculation of lunar data using NASA's SPICE toolkit

Project description

Latest Tag License: LGPL 3.0 Stargazers


spicedmoon logo

spicedmoon

Calculation of lunar data using NASA's SPICE toolkit.

About the project

spicedmoon is a Python toolkit built on top of SPICE (Acton, 1996), through spiceypy (Annex et Al. 2020) for computing high-precision lunar observational geometry.
It provides a high-level interface for the retrieval of selenographic coordinates, Sun-Moon-observer geometry, lunar phase angle and sign, azimuth & zenith, and more, from arbitrary observer locations on Earth or the Moon.

The library offers two complementary computation methodologies:

1. Custom-Body Method (Custom Kernel Based)

This method reproduces the workflow used in classic C/SPICE pipelines: a temporary SPK and frame kernel is generated for the observer, which becomes a synthetic SPICE "body". Geometry is then computed using NAIF high-level routines like subpnt (INTERCEPT/ELLIPSOID), subslr, spkezr, orpxform, and local-level frames.

This method uses fully SPICE-native geometry, but needs to generate temporary kernel files for the custom body, or use pre-existing ones.

2. Direct Geometry Method (No Custom Kernels)

A lightweight alternative that computes geometry via pure vector mathematics:

SPK ephemerides are don via spkpos, and frame transformations via pxform, but ellipsoid intersections are done analytically and lunar phase is computed from vector angles. This methodology works without temporary observer kernels.

This method yields results very similar to method 1's ones, and fits better in environments where generating kernel files is undesirable.

Getting started

Prerequisites

  • python>=3.7

Kernels

In order to use the package, a directory with all the kernels must be downloaded.

That directory must contain the following kernels:

Installation

You can install spicedmoon either from the source directory (for development) or directly from PyPI.

From source (editable mode):

pip install -e .

From PyPI (recommended):

pip install spicedmoon

Usage

spicedmoon exposes several high-level functions grouped into two computation methodologies: the Direct Geometry Method (no custom kernels) and the Custom-Body Method (kernel-based). Both approaches compute lunar geometry, but differ in how observer locations are represented.

Below you will find the recommended functions, organized by methodology.

1. Direct Geometry Method (No Custom Kernels)

These functions compute all geometry analytically, without generating observer kernels. They are lightweight, fast, and ideal for simulation or scripting environments.

1.1 get_moon_datas_llhs

Computes lunar geometry from planetographic coordinates (latitude, longitude, height) in degrees and km.

Example
from spicedmoon import get_moon_datas_llhs

llhs = [(40.0, -3.5, 0.7)]        # (lat, lon, height_km)
times = ["2025-01-10 00:00:00"]
kernels_path = "/path/to/kernels"

md = get_moon_datas_llhs(llhs, times, kernels_path)[0]

print(md.lon_obs, md.lat_obs) # Observer's selenographic longitude and latitude in degrees
# 1.4414527418916223 -5.396883424855335
print(md.azimuth, md.zenith) # Azimuth and zenith of the target in degrees. Az is measured from North towards East.
# 265.02364998605594 45.359639487448284
print(md.mpa_deg)  # signed moon phase angle in degrees
# -51.31044215492803

1.2 get_moon_datas_xyzs

Same as above, but receives rectangular coordinates in the chosen source frame.

Useful when your application already works with Earth-fixed or inertial positions.

Example
from spicedmoon import get_moon_datas_xyzs

xyz = [(4510.0, 3480.0, 4060.0)]     # km, in source frame (default: J2000)
times = ["2025-01-10 00:00:00"]
kernels_path = "/path/to/kernels"

md = get_moon_datas_xyzs(xyz, times, kernels_path)[0]
print(md.lon_obs, md.lat_obs) # Observer's selenographic longitude and latitude in degrees
# 2.321034649559275 -5.3589043558009095
print(md.lon_sun_rad, md.lat_sun_rad) # Sun's selenographic longitude and latitude in radians
# 0.9199491298659819 -0.02542042276583308
print(md.mpa_deg)  # signed moon phase angle in degrees
# -50.433894317010626

2. Custom-Body Method (Kernel-Based)

This method creates a temporary SPK + frame kernel for each observer, reproducing the classic C/SPICE workflow used by NAIF.
Use this method when absolute consistency with SPICE’s own geometry routines is required (e.g., comparing with legacy C pipelines).

2.1 get_moon_datas

Computes lunar geometry by:

  • Creating a custom observer body,
  • Generating an SPK for that body,
  • Loading it into SPICE,
  • Using high-level routines like subpnt, subslr, spkezr, and pxform.
Example
from spicedmoon import get_moon_datas

lat = 40.0
lon = -3.5
alt_m = 700     # altitude in meters here
times = ["2025-01-10 00:00:00"]
kernels_path="/path/to/kernels"

md = get_moon_datas(lat, lon, alt_m, times, kernels_path)[0]
print(md.lon_obs, md.lat_obs) # Observer's selenographic longitude and latitude in degrees
# 1.4414527418916219 -5.396888519065468
print(md.azimuth, md.zenith) # Azimuth and zenith of the target in degrees. Az is measured from North towards East.
# 265.02364998605594 45.359639487448284
print(md.mpa_deg)  # signed moon phase angle in degrees
# -51.31044215492803

2.2 get_moon_datas_from_extra_kernels

Allows using pre-existing custom-body kernels (e.g., Earth station networks).

Useful when you already have .bsp and .tf files describing station locations.

One must specify the local frame in observer_frame to correctly calculate zenith and azimuth. This frame must be present in the custom extra kernel files.

from spicedmoon import get_moon_datas_from_extra_kernels

times = ["2025-01-10 00:00:00"],
kernels_path = "/path/to/kernels"

md = get_moon_datas_from_extra_kernels(
    times,
    kernels_path,
    extra_kernels=["stations.bsp", "stations.tf"],
    extra_kernels_path="/path/to/extra/kernels",
    observer_name="DSS-14",
    observer_frame="DSS_LOCAL_LEVEL",
)[0]

2.3 get_moon_datas_from_moon

Custom-body variant for observers located on the Moon.

This function is analogous to get_moon_datas, but assumes the body of reference for planetographic coordinates is the Moon instead of the Earth. It generates a custom observer body on the lunar surface and uses SPICE to compute selenographic geometry from that location.

from spicedmoon import get_moon_datas_from_moon

lat = 10
lon = 45
alt_m = 10000
times = ["2025-01-10 00:00:00"]
kernels_path = "/path/to/kernels"

md = get_moon_datas_from_moon(lat, lon, alt_m, times, kernels_path)[0]
print(md.lon_obs, md.lat_obs) # Observer's selenographic longitude and latitude in degrees
# 44.999999999999986 10.000144376768828
print(md.azimuth, md.zenith) # Azimuth and zenith of the target in degrees. Az is measured from North towards East.
# 180.0 19.97647138243788
print(md.mpa_deg)  # signed moon phase angle in degrees
# -13.767196747269022

3. Lunar + Solar Geometry

3.1 get_sun_moon_datas

This helper computes solar geometry regarding to the moon and returns a MoonSunData structure.

Useful when you need only Sun position in respect to the moon.

from spicedmoon import get_sun_moon_datas

times = ["2025-01-10 00:00:00"]
kernels_path = "/path/to/kernels"

msd = get_sun_moon_datas(times, kernels_path)[0]
print(msd.lon_sun_rad, msd.lat_sun_rad) # Sun's selenographic longitude and latitude in radians
# 0.9199491298659819 -0.025420447501053357
print(msd.dist_sun_moon_km, msd.dist_sun_moon_au) # Distance between the Sun and the Moon in kilometers and in astronomical units
# 147349561.92390758 0.9849709846767327

4. Data Structures

4.1 MoonData

Returned by all get_moon_datas* functions (both direct and custom-body).

Fields include:

  • dist_sun_moon_au : Distance between the Sun and the Moon (in astronomical units)
  • dist_sun_moon_km : Distance between the Sun and the Moon (in kilometers)
  • dist_obs_moon : Distance between the Observer and the Moon (in kilometers)
  • lon_sun_rad : Selenographic longitude of the Sun (in radians)
  • lat_obs : Selenographic latitude of the observer (in degrees)
  • lon_obs : Selenographic longitude of the observer (in degrees)
  • mpa_deg : Moon phase angle (in degrees)
  • azimuth : Azimuth angle (in degrees)
  • zenith : Zenith angle (in degrees)

4.2 MoonSunData

Returned by get_sun_moon_datas.

Contains:

  • lon_sun_rad : Selenographic longitude of the Sun in radians
  • lat_sun_rad : Selenographic latitude of the Sun in radians
  • dist_sun_moon_km : Distance between the Sun and the Moon in km
  • dist_sun_moon_au : Distance between the Sun and the Moon in AU

Comparing both methods

Under tests/ one can see mulitple scripts useful to check result differences between methodologies and against results from external library (ephem, pylunar, etc.).

Custom-Body and Direct-Geometry should the same results if performed as follows:

from spicedmoon import get_moon_datas, get_moon_datas_llhs
# lat & lon are floats with the geographic coordinates in decimal degrees
# alt is a float with the altitude over sea-level in meters
# dts_str is a str with the date&time in a SPICE-compatible format
# kpath is a str with pointing to the kernels directory path
mds = get_moon_datas(
    lat, lon, alt, dts_str, kpath, earth_as_zenith_observer=False
)
mds = get_moon_datas_llhs(
    [(lat, lon, alt / 1000) for _ in range(len(dts_str))],
    dts_str,
    kpath,
)

In the following figure we can see that the relative differences are extremely low.

Relative Differences: Custom-Body VS Direct-Geometry

Structure

The package is divided in multiple submodules, each dealing with different calculations and functionalities. Its structure can be represented in a UML diagram:

UML diagram

Authors

License

Distributed under the LGPL-v3 License. See LGPL v3 for more information.

References

  • Acton, C. H. (1996). Ancillary data services of NASA's Navigation and Ancillary Information Facility. Planetary and Space Science, 44(1), 65-70, https://doi.org/10.1016/0032-0633(95)00107-7
  • Annex, A. M., Pearson, B., Seignovert, B., Carcich, B. T., Eichhorn, H., Mapel, J. A., von Forstner, J. L., Freiherr, McAuliffe, J., Diaz del Rio, J., Berry, K. L., Aye, Klaus-Michael A., Stefko, M. and de Val-Borro, M., Kulumani, S. & Murakami, S. (2020). SpiceyPy: a Pythonic Wrapper for the SPICE Toolkit. Journal of Open Source Software, 5(46), 2050, https://doi.org/10.21105/joss.02050

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spicedmoon-1.1.0.tar.gz (157.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spicedmoon-1.1.0-py3-none-any.whl (30.1 kB view details)

Uploaded Python 3

File details

Details for the file spicedmoon-1.1.0.tar.gz.

File metadata

  • Download URL: spicedmoon-1.1.0.tar.gz
  • Upload date:
  • Size: 157.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.25

File hashes

Hashes for spicedmoon-1.1.0.tar.gz
Algorithm Hash digest
SHA256 f48f38fef5abdcdbf93295b431c0cad6914055aac19ca783569b85c95183ad1c
MD5 6f2771981d2eb4e54125089cd2067e16
BLAKE2b-256 8b9d05c59f4a59c3cdce3e05c13ddb8990c6904bb9b26df05f2f26db8364f52b

See more details on using hashes here.

File details

Details for the file spicedmoon-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: spicedmoon-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.25

File hashes

Hashes for spicedmoon-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8f94675cba89ab40b32940bc581239d8fb1d38cbe520654b06b82109ad91b7f7
MD5 9ad3895693577827fbdbe3d051f886ce
BLAKE2b-256 2e456c0d01d40ff800844948dc31948c87799a8dc875d7b03fd6b9e050a482eb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page