From 5ef6893a3f49c4da7c41e608924b8d22c72bf904 Mon Sep 17 00:00:00 2001 From: Andrew Annex <2126916+AndrewAnnex@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:28:04 -0800 Subject: [PATCH 01/10] start of custom extension for sphinx to use standard sphinx styling --- docs/_static/css/pyscript_editor.css | 52 ++++++++ docs/conf.py | 18 +++ docs/pyodide.rst | 89 ++++++------- docs/pyscript_editor.py | 191 +++++++++++++++++++++++++++ 4 files changed, 302 insertions(+), 48 deletions(-) create mode 100644 docs/_static/css/pyscript_editor.css create mode 100644 docs/pyscript_editor.py diff --git a/docs/_static/css/pyscript_editor.css b/docs/_static/css/pyscript_editor.css new file mode 100644 index 00000000..6eac0acf --- /dev/null +++ b/docs/_static/css/pyscript_editor.css @@ -0,0 +1,52 @@ +/* + * Make the PyScript py-editor shadow-DOM slot look like a Sphinx code block. + * + * The outer .highlight.highlight-python div is already styled by your theme. + * These rules target the web-component internals that PyScript exposes. + */ + +/* Match the min-height to a reasonable number of visible lines */ +py-editor { + display: block; + min-height: 6em; +} + +/* + * PyScript 2024+ exposes a --py-editor-* set of custom properties. + * Mirror the values your Sphinx theme uses for
blocks.
+ */
+py-editor {
+ --py-editor-font-family: var(
+ --code-font-family,
+ "SFMono-Regular",
+ Consolas,
+ "Liberation Mono",
+ Menlo,
+ monospace
+ );
+ --py-editor-font-size: var(--code-font-size, 0.875em);
+ --py-editor-background: var(
+ --code-background,
+ var(--color-code-background, #f8f8f8)
+ );
+ --py-editor-foreground: var(
+ --code-foreground,
+ var(--color-foreground, #333)
+ );
+}
+
+/*
+ * Remove PyScript's own border/shadow so the outer Sphinx .highlight
+ * border is the only one visible.
+ */
+py-editor::part(editor) {
+ border: none;
+ box-shadow: none;
+ border-radius: 0;
+ padding: 0.5em 1em;
+}
+
+/* Run button: nudge it to sit inside the highlight box */
+py-editor::part(run-button) {
+ border-radius: 0 0 4px 0;
+}
diff --git a/docs/conf.py b/docs/conf.py
index b7e77bc0..fe964c7d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,8 +13,11 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
+import os
import sys
+sys.path.insert(0, os.path.abspath("."))
+
sys.setrecursionlimit(15000)
# If extensions (or modules to document with autodoc) are in another directory,
@@ -85,7 +88,22 @@
"sphinx_copybutton",
"myst_parser",
"sphinx_rtd_theme",
+ "pyscript_editor",
]
+
+# Global defaults for pyscript (all optional)
+pyscript_version = "2026.2.1"
+pyscript_env = "shared"
+pyscript_config = "pyscript.json"
+pyscript_mini_coi = "mini-coi.js" # "" to disable
+# Default is True — set to False to keep the line numbers
+pyscript_hide_gutters = True
+
+html_static_path = ["_static"]
+
+html_css_files = ["css/pyscript_editor.css"]
+
+
# conf for autodoc typehints
autodoc_typehints = "both"
diff --git a/docs/pyodide.rst b/docs/pyodide.rst
index 8f517681..1ff43089 100644
--- a/docs/pyodide.rst
+++ b/docs/pyodide.rst
@@ -68,75 +68,68 @@ Try updating the plot below to plot the barycenter of Mars or Mercury!
Various imports and setup:
~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. raw:: html
-
-
-
-
-
+.. py-editor::
+
+ import numpy as np
+ import matplotlib
+ matplotlib.use("AGG")
+ import matplotlib.pyplot as plt
+ from pyscript import display
+ import spiceypy as spice
+ print(f'SpiceyPy for {spice.tkvrsn("TOOLKIT")} ready!')
+
Load kernels
~~~~~~~~~~~~~~~~~~~~~
-.. raw:: html
+.. py-editor::
+
+ # Load kernels: leap seconds + planetary ephemeris
+ spice.furnsh("naif0012.tls")
+ spice.furnsh("de440s_2000_to_2020_simplified.bsp")
+ print(f'Loaded {spice.ktotal("ALL")} kernels.')
+
-
Specify the dates to sample:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. raw:: html
+.. py-editor::
-
+ # Grab 2000 dates over 8 years
+ et0 = spice.str2et("2000-01-01")
+ et1 = spice.str2et("2008-01-01")
+ ets = np.linspace(et0, et1, 2000)
+ print(f'ets array has len: {len(ets)}')
+
+
Get the positions vector
~~~~~~~~~~~~~~~~~~~~~~~~
-.. raw:: html
+.. py-editor::
-
+ # Venus position relative to Earth in ecliptic J2000 (km)
+ positions, _ = spice.spkpos("VENUS BARYCENTER", ets, "ECLIPJ2000", "NONE", "EARTH")
+ print(f'Got {len(positions)} positions of the Venus Barycenter')
Plot it on the ecliptic plane.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. raw:: html
-
-
-
+.. py-editor::
+ :target: mpl
+
+ fig, ax = plt.subplots()
+ ax.plot(positions[:, 0], positions[:, 1], color="black", linewidth=0.5)
+ ax.plot(0, 0, "o", color="blue", markersize=6, label="Earth")
+ ax.set_title("Geocentric Orbit of Venus (2000–2008)", fontsize=14)
+ ax.legend()
+ ax.set_aspect("equal")
+ display(fig, target="mpl", append=False)
+ plt.close('all')
+
diff --git a/docs/pyscript_editor.py b/docs/pyscript_editor.py
new file mode 100644
index 00000000..47d9e12c
--- /dev/null
+++ b/docs/pyscript_editor.py
@@ -0,0 +1,191 @@
+"""
+Sphinx extension: pyscript_editor
+----------------------------------
+Adds a ``.. py-editor::`` directive that renders a PyScript editor
+wrapped in the same ``div.highlight.highlight-python`` structure that
+Sphinx/Pygments produces, so it inherits your theme's code-block styling.
+
+Usage in conf.py
+----------------
+ extensions = [..., "pyscript_editor"]
+
+ # Optional global defaults (all overridable per-directive):
+ pyscript_version = "2026.2.1" # PyScript release
+ pyscript_env = "shared" # py-editor env attribute
+ pyscript_config = "pyscript.json" # py-editor config attribute
+ pyscript_mini_coi = "mini-coi.js" # path to mini-coi shim;
+ # set to "" to skip
+
+Usage in .rst files
+-------------------
+Basic (uses global defaults from conf.py)::
+
+ .. py-editor::
+
+ import numpy as np
+ print(np.__version__)
+
+Override any option per block::
+
+ .. py-editor::
+ :env: isolated
+ :config: other.json
+
+ print("hello")
+
+The ``mini-coi.js`` script and the PyScript stylesheet/module are injected
+only once per page, no matter how many ``.. py-editor::`` directives appear.
+"""
+
+from __future__ import annotations
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+from sphinx.application import Sphinx
+from sphinx.util import logging
+
+logger = logging.getLogger(__name__)
+
+# ---------------------------------------------------------------------------
+# Raw-HTML node helpers
+# ---------------------------------------------------------------------------
+
+
+def _raw(html: str) -> nodes.raw:
+ return nodes.raw("", html, format="html")
+
+
+# ---------------------------------------------------------------------------
+# One-per-page head injection (idempotent via env metadata)
+# ---------------------------------------------------------------------------
+
+_HEAD_KEY = "pyscript_head_injected"
+_ENV_KEY = "pyscript_env_config_registered"
+
+
+_HIDE_GUTTERS_JS = """
+
+"""
+
+
+def _head_html(mini_coi: str, version: str, hide_gutters: bool) -> str:
+ parts = []
+ if mini_coi:
+ parts.append(f'')
+ parts.append(
+ f''
+ )
+ parts.append(
+ f''
+ )
+ if hide_gutters:
+ parts.append(_HIDE_GUTTERS_JS)
+ return "\n".join(parts) + "\n"
+
+
+# ---------------------------------------------------------------------------
+# Directive
+# ---------------------------------------------------------------------------
+
+
+class PyEditorDirective(Directive):
+ """``.. py-editor::`` directive."""
+
+ has_content = True
+ optional_arguments = 0
+ option_spec = {
+ "env": directives.unchanged,
+ "config": directives.unchanged,
+ "target": directives.unchanged,
+ }
+
+ def run(self) -> list[nodes.Node]:
+ env = self.state.document.settings.env # Sphinx BuildEnvironment
+ cfg = env.config
+
+ # ---- resolve options, falling back to conf.py values ----
+ version = cfg.pyscript_version
+ mini_coi = cfg.pyscript_mini_coi
+ hide_gutters = cfg.pyscript_hide_gutters
+ ed_env = self.options.get("env", cfg.pyscript_env)
+ ed_cfg = self.options.get("config", cfg.pyscript_config)
+ ed_target = self.options.get("target", None)
+
+ result: list[nodes.Node] = []
+
+ # ---- inject assets once per document ----
+ injected = getattr(env, _HEAD_KEY, set())
+ if env.docname not in injected:
+ result.append(_raw(_head_html(mini_coi, version, hide_gutters)))
+ injected.add(env.docname)
+ setattr(env, _HEAD_KEY, injected)
+
+ # ---- emit config attr only on the first editor for each (page, env) ----
+ # PyScript reads the config once per named environment; repeating it is harmless
+ # but emitting it only on the first occurrence keeps the HTML clean.
+ env_configs = getattr(env, _ENV_KEY, set())
+ env_key = (env.docname, ed_env)
+ if env_key not in env_configs:
+ config_part = f' config="{ed_cfg}"' if ed_cfg else ""
+ env_configs.add(env_key)
+ setattr(env, _ENV_KEY, env_configs)
+ else:
+ config_part = ""
+
+ # ---- build the editor HTML ----
+ code = "\n".join(self.content)
+ indented = "\n".join(" " + line for line in code.splitlines())
+
+ editor_html = (
+ '\n'
+ '\n'
+ f'\n"
+ "\n"
+ "\n"
+ )
+ if ed_target:
+ editor_html += f'\n'
+
+ result.append(_raw(editor_html))
+ return result
+
+
+# ---------------------------------------------------------------------------
+# Extension setup
+# ---------------------------------------------------------------------------
+
+
+def setup(app: Sphinx) -> dict:
+ app.add_config_value("pyscript_version", "2026.2.1", "html")
+ app.add_config_value("pyscript_env", "shared", "html")
+ app.add_config_value("pyscript_config", "pyscript.json", "html")
+ app.add_config_value("pyscript_mini_coi", "mini-coi.js", "html")
+ app.add_config_value("pyscript_hide_gutters", True, "html")
+
+ app.add_directive("py-editor", PyEditorDirective)
+
+ return {
+ "version": "0.1.0",
+ "parallel_read_safe": True,
+ "parallel_write_safe": True,
+ }
From 699b3f9dc2dc46eb18c14267a3a4314cd703f14d Mon Sep 17 00:00:00 2001
From: Andrew Annex <2126916+AndrewAnnex@users.noreply.github.com>
Date: Sun, 8 Mar 2026 17:28:25 -0700
Subject: [PATCH 02/10] working and started refactor for other_stuff lesson,
needs more consolidation and work though
---
docs/conf.py | 2 +
docs/other_stuff.rst | 1616 ++++++++++++++++----------------
docs/pyscript_editor.py | 99 +-
docs/pyscript_min.json | 6 +
docs/pyscript_other_stuff.json | 11 +
docs/remote_sensing.rst | 6 +-
6 files changed, 901 insertions(+), 839 deletions(-)
create mode 100644 docs/pyscript_min.json
create mode 100644 docs/pyscript_other_stuff.json
diff --git a/docs/conf.py b/docs/conf.py
index fe964c7d..0697b1ad 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -218,6 +218,8 @@
# directly to the root of the documentation.
html_extra_path = [
"pyscript.json",
+ "pyscript_min.json",
+ "pyscript_other_stuff.json",
"mini-coi.js",
]
diff --git a/docs/other_stuff.rst b/docs/other_stuff.rst
index 5f98f874..ed36c2f3 100644
--- a/docs/other_stuff.rst
+++ b/docs/other_stuff.rst
@@ -101,13 +101,21 @@ SpiceyPy API Documentation
A SpiceyPy function's parameters specification is available using the
built-in Python help system.
+.. py-editor::
+ :env: other
+ :config: pyscript_other_stuff.json
+ :setup:
+
+ import spiceypy
+
For example, the Python help function
-.. code-block:: python
+.. py-editor::
+ :env: other
- import spiceypy
- help(spiceypy.str2et)
+ import spiceypy
+ help(spiceypy.str2et)
describes of the str2et function's parameters, while the document
@@ -255,7 +263,7 @@ particular Spice subsystems:
time.req
windows.req
-NAIF Users Guides (\*.ug) describe the proper use of particular SpiceyPy
+NAIF Users Guides (\*.ug) describe the proper use of particular Spice command line
tools:
::
@@ -361,22 +369,11 @@ This SpiceyPy API documentation (the same information as in the website
but without hyperlinks) is also available from the Python built-in help
system:
-::
-
- >>> help ( spiceypy.str2et )
- Help on function str2et in module spiceypy.spiceypy:
-
- str2et(*args, **kwargs)
- Convert a string representing an epoch to a double precision
- value representing the number of TDB seconds past the J2000
- epoch corresponding to the input epoch.
+.. py-editor::
+ :env: other
- ...
-
- :param time: A string representing an epoch.
- :type time: str
- :return: The equivalent value in seconds past J2000, TDB.
- :rtype: float
+ import spiceypy
+ help(spiceypy.str2et) # hit run button to see help
Text kernels
@@ -439,9 +436,7 @@ Things to know:
signaling an error on any attempt to read non-native text
kernels.
-Text kernel format
-
-Scalar assignments.
+Text kernel format scalar assignments.
.. code-block:: text
@@ -513,22 +508,32 @@ First, create a meta text kernel:
You can use two versions of a meta kernel with code examples (kpool.tm)
in this lesson. Either a kernel with explicit path information:
-.. code-block:: text
+.. py-editor::
+ :env: other
- KPL/MK
+ mk=r"""
+ KPL/MK
- \begindata
+ \begindata
- KERNELS_TO_LOAD = ( 'kernels/spk/de405s.bsp',
- 'kernels/pck/pck00008.tpc',
- 'kernels/lsk/naif0008.tls' )
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/de405s.bsp',
+ 'kernels/pck/pck00008.tpc')
+
+ \begintext
+ """
+ with open('kpool.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file kpool.tm')
- \begintext
… or a more generic meta kernel using the PATH_VALUES/PATH_SYMBOLS
functionality to declare path names as variables:
-.. code-block:: text
+.. py-editor::
+ :env: other
+
+ mk=r"""
KPL/MK
@@ -558,86 +563,81 @@ functionality to declare path names as variables:
'$PCK/pck00008.tpc' )
\begintext
+ """
+ with open('kpool_generic.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file kpool_generic.tm')
Now the solution source code:
-.. code-block:: python
-
- from __future__ import print_function
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
-
- def kpool():
-
- #
- # Assign the path name of the meta kernel to META.
- #
- META = 'kpool.tm'
-
-
- #
- # Load the meta kernel then use KTOTAL to interrogate the SPICE
- # kernel subsystem.
- #
- spiceypy.furnsh( META )
-
-
- count = spiceypy.ktotal( 'ALL' )
- print( 'Kernel count after load: {0}\n'.format(count))
-
-
- #
- # Loop over the number of files; interrogate the SPICE system
- # with spiceypy.kdata for the kernel names and the type.
- # 'found' returns a boolean indicating whether any kernel files
- # of the specified type were loaded by the kernel subsystem.
- # This example ignores checking 'found' as kernels are known
- # to be loaded.
- #
- for i in range(0, count):
- [ file, type, source, handle] = spiceypy.kdata(i, 'ALL');
- print( 'File {0}'.format(file) )
- print( 'Type {0}'.format(type) )
- print( 'Source {0}\n'.format(source) )
-
-
- #
- # Unload one kernel then check the count.
- #
- spiceypy.unload( 'kernels/spk/de405s.bsp')
- count = spiceypy.ktotal( 'ALL' )
-
- #
- # The subsystem should report one less kernel.
- #
- print( 'Kernel count after one unload: {0}'.format(count))
-
- #
- # Now unload the meta kernel. This action unloads all
- # files listed in the meta kernel.
- #
- spiceypy.unload( META )
-
-
- #
- # Check the count; spiceypy should return a count of zero.
- #
- count = spiceypy.ktotal( 'ALL')
- print( 'Kernel count after meta unload: {0}'.format(count))
-
-
- #
- # Done. Unload the kernels.
- #
- spiceypy.kclear
-
- if __name__ == '__main__':
- kpool()
-
-Run the code example
+.. py-editor::
+ :env: other
+
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+
+ # Assign the path name of the meta kernel to META.
+ def kpool(META='kpool.tm'):
+ #
+ # Load the meta kernel then use KTOTAL to interrogate the SPICE
+ # kernel subsystem.
+ #
+ spiceypy.furnsh(META)
+
+ count = spiceypy.ktotal( 'ALL' )
+ print( f'Kernel count after load: {count}\n' )
+
+ #
+ # Loop over the number of files; interrogate the SPICE system
+ # with spiceypy.kdata for the kernel names and the type.
+ # 'found' returns a boolean indicating whether any kernel files
+ # of the specified type were loaded by the kernel subsystem.
+ # This example ignores checking 'found' as kernels are known
+ # to be loaded.
+ #
+ for i in range(count):
+ [ file, type, source, handle] = spiceypy.kdata(i, 'ALL');
+ print( 'File {0}'.format(file) )
+ print( 'Type {0}'.format(type) )
+ print( 'Source {0}\n'.format(source) )
+
+ #
+ # Unload one kernel then check the count.
+ #
+ spiceypy.unload( 'kernels/spk/de405s.bsp')
+ count = spiceypy.ktotal( 'ALL' )
+
+ #
+ # The subsystem should report one less kernel.
+ #
+ print( f'Kernel count after one unload: {count}\n')
+
+ #
+ # Now unload the meta kernel. This action unloads all
+ # files listed in the meta kernel.
+ #
+ spiceypy.unload( META )
+
+ #
+ # Check the count; spiceypy should return a count of zero.
+ #
+ count = spiceypy.ktotal( 'ALL')
+ print( f'Kernel count after meta unload: {count}')
+
+ #
+ # Done. Unload the kernels.
+ #
+ spiceypy.kclear()
+
+ if __name__ == '__main__':
+ kpool()
+ kpool(META="kpool_generic.tm")
+
+
+
+Run the code example locally or by clicking the run button above.
First we see the number of all loaded kernels returned from the
spiceypy.ktotal call.
@@ -671,6 +671,8 @@ direct load of the kernel with a spiceypy.furnsh call.
Kernel count after one unload: 3
Kernel count after meta unload: 0
+this repeats for the kpool_generic.tm file.
+
Lesson 2: The Kernel Pool
------------------------------
@@ -695,8 +697,10 @@ pool.
For the code examples, use this generic text kernel (kervar.tm)
containing PCK-type data, kernels to load, and example time strings:
-.. code-block:: text
+.. py-editor::
+ :env: other
+ mk=r"""
KPL/MK
Name the kernels to load. Use path symbols.
@@ -759,6 +763,10 @@ containing PCK-type data, kernels to load, and example time strings:
)
\begintext
+ """
+ with open('kervar.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file kervar.tm')
The main references for pool routines are found in the help command, the
CSPICE source files or the API documentation for the particular
@@ -769,139 +777,139 @@ routines.
Code Solution
^^^^^^^^^^^^^
-.. code-block:: python
-
- from __future__ import print_function
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
- from spiceypy.utils.support_types import SpiceyError
-
- def kervar():
+.. py-editor::
+ :env: other
+
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+ from spiceypy.utils.support_types import SpiceyError
+
+ def kervar():
+
+ #
+ # Define the max number of kernel variables
+ # of concern for this examples.
+ #
+ N_ITEMS = 20
+
+ #
+ # Load the example kernel containing the kernel variables.
+ # The kernels defined in KERNELS_TO_LOAD load into the
+ # kernel pool with this call.
+ #
+ spiceypy.furnsh( 'kervar.tm' )
+
+ #
+ # Initialize the start value. This value indicates
+ # index of the first element to return if a kernel
+ # variable is an array. START = 0 indicates return everything.
+ # START = 1 indicates return everything but the first element.
+ #
+ START = 0
+
+ #
+ # Set the template for the variable names to find. Let's
+ # look for all variables containing the string RING.
+ # Define this with the wildcard template '*RING*'. Note:
+ # the template '*RING' would match any variable name
+ # ending with the RING string.
+ #
+ tmplate = '*RING*'
+
+ #
+ # We're ready to interrogate the kernel pool for the
+ # variables matching the template. spiceypy.gnpool tells us:
+ #
+ # 1. Does the kernel pool contain any variables that
+ # match the template (value of found).
+ # 2. If so, how many variables?
+ # 3. The variable names. (cvals, an array of strings)
+ #
+
+ try:
+ cvals = spiceypy.gnpool( tmplate, START, N_ITEMS )
+ print( 'Number variables matching template: {0}'.\
+ format( len(cvals)) )
+ except SpiceyError:
+ print( 'No kernel variables matched template.' )
+ return
+
+
+ #
+ # Okay, now we know something about the kernel pool
+ # variables of interest to us. Let's find out more...
+ #
+ for cval in cvals:
+
+ #
+ # Use spiceypy.dtpool to return the dimension and type,
+ # C (character) or N (numeric), of each pool
+ # variable name in the cvals array. We know the
+ # kernel data exists.
+ #
+ [dim, type] = spiceypy.dtpool( cval )
+
+ print( '\n' + cval )
+ print( ' Number items: {0} Of type: {1}\n'.\
+ format(dim, type) )
+
+ #
+ # Test character equality, 'N' or 'C'.
+ #
+ if type == 'N':
- #
- # Define the max number of kernel variables
- # of concern for this examples.
- #
- N_ITEMS = 20
+ #
+ # If 'type' equals 'N', we found a numeric array.
+ # In this case any numeric array will be an array
+ # of double precision numbers ('doubles').
+ # spiceypy.gdpool retrieves doubles from the
+ # kernel pool.
+ #
+ dvars = spiceypy.gdpool( cval, START, N_ITEMS )
+ for dvar in dvars:
+ print(' Numeric value: {0:20.6f}'.format(dvar))
- #
- # Load the example kernel containing the kernel variables.
- # The kernels defined in KERNELS_TO_LOAD load into the
- # kernel pool with this call.
- #
- spiceypy.furnsh( 'kervar.tm' )
+ elif type == 'C':
- #
- # Initialize the start value. This value indicates
- # index of the first element to return if a kernel
- # variable is an array. START = 0 indicates return everything.
- # START = 1 indicates return everything but the first element.
- #
- START = 0
-
- #
- # Set the template for the variable names to find. Let's
- # look for all variables containing the string RING.
- # Define this with the wildcard template '*RING*'. Note:
- # the template '*RING' would match any variable name
- # ending with the RING string.
- #
- tmplate = '*RING*'
+ #
+ # If 'type' equals 'C', we found a string array.
+ # spiceypy.gcpool retrieves string values from the
+ # kernel pool.
+ #
+ cvars = spiceypy.gcpool( cval, START, N_ITEMS )
- #
- # We're ready to interrogate the kernel pool for the
- # variables matching the template. spiceypy.gnpool tells us:
- #
- # 1. Does the kernel pool contain any variables that
- # match the template (value of found).
- # 2. If so, how many variables?
- # 3. The variable names. (cvals, an array of strings)
- #
+ for cvar in cvars:
+ print(' String value: {0}\n'.format(cvar))
- try:
- cvals = spiceypy.gnpool( tmplate, START, N_ITEMS )
- print( 'Number variables matching template: {0}'.\
- format( len(cvals)) )
- except SpiceyError:
- print( 'No kernel variables matched template.' )
- return
+ else:
+ #
+ # This block should never execute.
+ #
+ print('Unknown type. Code error.')
- #
- # Okay, now we know something about the kernel pool
- # variables of interest to us. Let's find out more...
- #
- for cval in cvals:
-
- #
- # Use spiceypy.dtpool to return the dimension and type,
- # C (character) or N (numeric), of each pool
- # variable name in the cvals array. We know the
- # kernel data exists.
- #
- [dim, type] = spiceypy.dtpool( cval )
-
- print( '\n' + cval )
- print( ' Number items: {0} Of type: {1}\n'.\
- format(dim, type) )
-
- #
- # Test character equality, 'N' or 'C'.
- #
- if type == 'N':
-
- #
- # If 'type' equals 'N', we found a numeric array.
- # In this case any numeric array will be an array
- # of double precision numbers ('doubles').
- # spiceypy.gdpool retrieves doubles from the
- # kernel pool.
- #
- dvars = spiceypy.gdpool( cval, START, N_ITEMS )
- for dvar in dvars:
- print(' Numeric value: {0:20.6f}'.format(dvar))
-
- elif type == 'C':
-
- #
- # If 'type' equals 'C', we found a string array.
- # spiceypy.gcpool retrieves string values from the
- # kernel pool.
- #
- cvars = spiceypy.gcpool( cval, START, N_ITEMS )
-
- for cvar in cvars:
- print(' String value: {0}\n'.format(cvar))
-
- else:
-
- #
- # This block should never execute.
- #
- print('Unknown type. Code error.')
+ #
+ # Now look at the time variable EXAMPLE_TIMES. Extract this
+ # value as an array of doubles.
+ #
+ dvars = spiceypy.gdpool( 'EXAMPLE_TIMES', START, N_ITEMS )
- #
- # Now look at the time variable EXAMPLE_TIMES. Extract this
- # value as an array of doubles.
- #
- dvars = spiceypy.gdpool( 'EXAMPLE_TIMES', START, N_ITEMS )
+ print( 'EXAMPLE_TIMES' )
- print( 'EXAMPLE_TIMES' )
+ for dvar in dvars:
+ print(' Time value: {0:20.6f}'.format(dvar))
- for dvar in dvars:
- print(' Time value: {0:20.6f}'.format(dvar))
+ #
+ # Done. Unload the kernels.
+ #
+ spiceypy.kclear()
- #
- # Done. Unload the kernels.
- #
- spiceypy.kclear
+ if __name__ == '__main__':
+ kervar()
- if __name__ == '__main__':
- kervar()
Run the code example
@@ -1011,154 +1019,167 @@ rectangular, cylindrical, and spherical systems.
Code Solution
^^^^^^^^^^^^^
-.. code-block:: python
-
- from __future__ import print_function
- from builtins import input
- import sys
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
-
- def coord():
-
- #
- # Define the inertial and non inertial frame names.
- #
- # Initialize variables or set type. All variables
- # used in a PROMPT construct must be initialized
- # as strings.
- #
- INRFRM = 'J2000'
- NONFRM = 'IAU_EARTH'
- r2d = spiceypy.dpr()
-
- #
- # Load the needed kernels using a spiceypy.furnsh call on the
- # meta kernel.
- #
- spiceypy.furnsh( 'coord.tm' )
-
- #
- # Prompt the user for a time string. Convert the
- # time string to ephemeris time J2000 (ET).
- #
- timstr = input( 'Time of interest: ')
- et = spiceypy.str2et( timstr )
-
- #
- # Access the kernel pool data for the triaxial radii of the
- # Earth, rad[1][0] holds the equatorial radius, rad[1][2]
- # the polar radius.
- #
- rad = spiceypy.bodvrd( 'EARTH', 'RADII', 3 )
-
- #
- # Calculate the flattening factor for the Earth.
- #
- # equatorial_radius - polar_radius
- # flat = ________________________________
- #
- # equatorial_radius
- #
- flat = (rad[1][0] - rad[1][2])/rad[1][0]
-
- #
- # Make the spiceypy.spkpos call to determine the apparent
- # position of the Moon w.r.t. to the Earth at 'et' in the
- # inertial frame.
- #
- [pos, ltime] = spiceypy.spkpos('MOON', et, INRFRM,
- 'LT+S','EARTH' )
-
- #
- # Show the current frame and time.
- #
- print( ' Time : {0}'.format(timstr) )
- print( ' Inertial Frame: {0}\n'.format(INRFRM) )
-
- #
- # First convert the position vector
- # X = pos(1), Y = pos(2), Z = pos(3), to RA/DEC.
- #
- [ range, ra, dec ] = spiceypy.recrad( pos )
-
- print(' Range/Ra/Dec' )
- print(' Range: {0:20.6f}'.format(range) )
- print(' RA : {0:20.6f}'.format(ra * r2d) )
- print(' DEC : {0:20.6f}'.format(dec* r2d) )
+.. py-editor::
+ :env: other
+
+ mk=r"""
+ KPL/MK
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/de405s.bsp',
+ 'kernels/pck/pck00008.tpc')
+ """
+ with open('coord.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file corrd.tm')
+
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+
+ def coord(timstr: str):
+ #
+ # Define the inertial and non inertial frame names.
+ #
+ # Initialize variables or set type. All variables
+ # used in a PROMPT construct must be initialized
+ # as strings.
+ #
+ INRFRM = 'J2000'
+ NONFRM = 'IAU_EARTH'
+ r2d = spiceypy.dpr()
+
+ #
+ # Load the needed kernels using a spiceypy.furnsh call on the
+ # meta kernel.
+ #
+ spiceypy.furnsh( 'coord.tm' )
+
+ #
+ # Convert the time string to ephemeris time J2000 (ET).
+ #
+ et = spiceypy.str2et( timstr )
+
+ #
+ # Access the kernel pool data for the triaxial radii of the
+ # Earth, rad[1][0] holds the equatorial radius, rad[1][2]
+ # the polar radius.
+ #
+ rad = spiceypy.bodvrd( 'EARTH', 'RADII', 3 )
+
+ #
+ # Calculate the flattening factor for the Earth.
+ #
+ # equatorial_radius - polar_radius
+ # flat = ________________________________
+ #
+ # equatorial_radius
+ #
+ flat = (rad[1][0] - rad[1][2])/rad[1][0]
+
+ #
+ # Make the spiceypy.spkpos call to determine the apparent
+ # position of the Moon w.r.t. to the Earth at 'et' in the
+ # inertial frame.
+ #
+ [pos, ltime] = spiceypy.spkpos('MOON', et, INRFRM,
+ 'LT+S','EARTH' )
+
+ #
+ # Show the current frame and time.
+ #
+ print( ' Time : {0}'.format(timstr) )
+ print( ' Inertial Frame: {0}\n'.format(INRFRM) )
+
+ #
+ # First convert the position vector
+ # X = pos(1), Y = pos(2), Z = pos(3), to RA/DEC.
+ #
+ [ range, ra, dec ] = spiceypy.recrad( pos )
+
+ print(' Range/Ra/Dec' )
+ print(' Range: {0:20.6f}'.format(range) )
+ print(' RA : {0:20.6f}'.format(ra * r2d) )
+ print(' DEC : {0:20.6f}'.format(dec* r2d) )
+
+ #
+ # ...latitudinal coordinates...
+ #
+ [ range, lon, lat ] = spiceypy.reclat( pos )
+ print(' Latitudinal ' )
+ print(' Rad : {0:20.6f}'.format(range) )
+ print(' Lon : {0:20.6f}'.format(lon * r2d) )
+ print(' Lat : {0:20.6f}'.format(lat * r2d) )
+
+ #
+ # ...spherical coordinates use the colatitude,
+ # the angle from the Z axis.
+ #
+ [ range, colat, lon ] = spiceypy.recsph( pos )
+ print( ' Spherical' )
+ print(' Rad : {0:20.6f}'.format(range) )
+ print(' Lon : {0:20.6f}'.format(lon * r2d) )
+ print(' Colat: {0:20.6f}'.format(colat * r2d) )
+
+ #
+ # Make the spiceypy.spkpos call to determine the apparent
+ # position of the Moon w.r.t. to the Earth at 'et' in the
+ # non-inertial, body fixed, frame.
+ #
+ [pos, ltime] = spiceypy.spkpos('MOON', et, NONFRM,
+ 'LT+S','EARTH')
+
+ print()
+ print( ' Non-inertial Frame: {0}'.format(NONFRM) )
+
+ #
+ # ...latitudinal coordinates...
+ #
+ [ range, lon, lat ] = spiceypy.reclat( pos )
+ print(' Latitudinal ' )
+ print(' Rad : {0:20.6f}'.format(range) )
+ print(' Lon : {0:20.6f}'.format(lon * r2d) )
+ print(' Lat : {0:20.6f}'.format(lat * r2d) )
+
+ #
+ # ...spherical coordinates use the colatitude,
+ # the angle from the Z axis.
+ #
+ [ range, colat, lon ] = spiceypy.recsph( pos )
+ print( ' Spherical' )
+ print(' Rad : {0:20.6f}'.format(range) )
+ print(' Lon : {0:20.6f}'.format(lon * r2d) )
+ print(' Colat: {0:20.6f}'.format(colat * r2d) )
+
+ #
+ # ...finally, convert the position to geodetic coordinates.
+ #
+ [ lon, lat, range ] = spiceypy.recgeo( pos, rad[1][0], flat )
+ print( ' Geodetic' )
+ print(' Rad : {0:20.6f}'.format(range) )
+ print(' Lon : {0:20.6f}'.format(lon * r2d) )
+ print(' Lat : {0:20.6f}'.format(lat * r2d) )
+ print()
+
+ #
+ # Done. Unload the kernels.
+ #
+ spiceypy.kclear()
+
+ # if running locally, uncomment below
+ #from builtins import input
+ #if __name__ == '__main__':
+ # timstr = input( 'Time of interest: ')
+ # coord(timstr)
+
+Run the code example:
+
+.. py-editor::
+ :env: other
+
+ coord("Feb 3 2002 TDB")
- #
- # ...latitudinal coordinates...
- #
- [ range, lon, lat ] = spiceypy.reclat( pos )
- print(' Latitudinal ' )
- print(' Rad : {0:20.6f}'.format(range) )
- print(' Lon : {0:20.6f}'.format(lon * r2d) )
- print(' Lat : {0:20.6f}'.format(lat * r2d) )
-
- #
- # ...spherical coordinates use the colatitude,
- # the angle from the Z axis.
- #
- [ range, colat, lon ] = spiceypy.recsph( pos )
- print( ' Spherical' )
- print(' Rad : {0:20.6f}'.format(range) )
- print(' Lon : {0:20.6f}'.format(lon * r2d) )
- print(' Colat: {0:20.6f}'.format(colat * r2d) )
-
- #
- # Make the spiceypy.spkpos call to determine the apparent
- # position of the Moon w.r.t. to the Earth at 'et' in the
- # non-inertial, body fixed, frame.
- #
- [pos, ltime] = spiceypy.spkpos('MOON', et, NONFRM,
- 'LT+S','EARTH')
-
- print()
- print( ' Non-inertial Frame: {0}'.format(NONFRM) )
-
- #
- # ...latitudinal coordinates...
- #
- [ range, lon, lat ] = spiceypy.reclat( pos )
- print(' Latitudinal ' )
- print(' Rad : {0:20.6f}'.format(range) )
- print(' Lon : {0:20.6f}'.format(lon * r2d) )
- print(' Lat : {0:20.6f}'.format(lat * r2d) )
-
- #
- # ...spherical coordinates use the colatitude,
- # the angle from the Z axis.
- #
- [ range, colat, lon ] = spiceypy.recsph( pos )
- print( ' Spherical' )
- print(' Rad : {0:20.6f}'.format(range) )
- print(' Lon : {0:20.6f}'.format(lon * r2d) )
- print(' Colat: {0:20.6f}'.format(colat * r2d) )
-
- #
- # ...finally, convert the position to geodetic coordinates.
- #
- [ lon, lat, range ] = spiceypy.recgeo( pos, rad[1][0], flat )
- print( ' Geodetic' )
- print(' Rad : {0:20.6f}'.format(range) )
- print(' Lon : {0:20.6f}'.format(lon * r2d) )
- print(' Lat : {0:20.6f}'.format(lat * r2d) )
- print()
-
- #
- # Done. Unload the kernels.
- #
- spiceypy.kclear
-
-
- if __name__ == '__main__':
- coord()
-
-Run the code example
Input “Feb 3 2002 TDB” to calculate the Moon's position. (the 'TDB' tag
indicates a Barycentric Dynamical Time value).
@@ -1302,119 +1323,127 @@ Code Solution
Caution: Be sure to assign sufficient string lengths for time
formats/pictures.
-.. code-block:: python
-
- from __future__ import print_function
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
-
- def xtic():
-
- #
- # Assign the META variable to the name of the meta-kernel
- # that contains the LSK kernel and create an arbitrary
- # time string.
- #
- CALSTR = 'Mar 15, 2003 12:34:56.789 AM PST'
- META = 'xtic.tm'
- AMBIGSTR = 'Mar 15, 79 12:34:56'
- T_FORMAT1 = 'Wkd Mon DD HR:MN:SC PDT YYYY ::UTC-7'
- T_FORMAT2 = 'Wkd Mon DD HR:MN ::UTC-7 YR (JULIAND.##### JDUTC)'
-
- #
- # Load the meta-kernel.
- #
- spiceypy.furnsh( META )
- print( 'Original time string : {0}'.format(CALSTR) )
-
- #
- # Convert the time string to the number of ephemeris
- # seconds past the J2000 epoch. This is the most common
- # internal time representation used by the CSPICE
- # system; CSPICE refers to this as ephemeris time (ET).
- #
- et = spiceypy.str2et( CALSTR )
- print( 'Corresponding ET : {0:20.6f}\n'.format(et) )
-
- #
- # Make a picture of an output format. Describe a Unix-like
- # time string then send the picture and the 'et' value through
- # spiceypy.timout to format and convert the ET representation
- # of the time string into the form described in
- # spiceypy.timout. The '::UTC-7' token indicates the time
- # zone for the `timstr' output - PDT. 'PDT' is part of the
- # output, but not a time system token.
- #
- timstr = spiceypy.timout( et, T_FORMAT1)
- print( 'Time in string format 1 : {0}'.format(timstr) )
-
- timstr = spiceypy.timout( et, T_FORMAT2)
- print( 'Time in string format 2 : {0}'.format(timstr) )
-
- #
- # Why create a picture by hand when spiceypy can do it for
- # you? Input a string to spiceypy.tpictr with the format of
- # interest. `ok' returns a boolean indicating whether an
- # error occurred while parsing the picture string, if so,
- # an error diagnostic message returns in 'xerror'. In this
- # example the picture string is known as correct.
- #
- pic = '12:34:56.789 P.M. PDT January 1, 2006'
- [ pictr, ok, xerror] = spiceypy.tpictr(pic)
-
- if not bool(ok):
- print( xerror )
- exit
-
-
- timstr = spiceypy.timout( et, pictr)
- print( 'Time in string format 3 : {0}'.format( timstr ) )
-
- #
- # Two digit year representations often cause problems due to
- # the ambiguity of the century. The routine spiceypy.tsetyr
- # gives the user the ability to set a default range for 2
- # digit year representation. SPICE uses 1969AD as the default
- # start year so the numbers inclusive of 69 to 99 represent
- # years 1969AD to 1999AD, the numbers inclusive of 00 to 68
- # represent years 2000AD to 2068AD.
- #
- # The defined time string 'AMBIGSTR' contains a two-digit
- # year. Since the SPICE base year is 1969, the time subsystem
- # interprets the string as 1979.
- #
- et1 = spiceypy.str2et( AMBIGSTR )
-
- #
- # Set 1980 as the base year causes SPICE to interpret the
- # time string's "79" as 2079.
- #
- spiceypy.tsetyr( 1980 )
- et2 = spiceypy.str2et( AMBIGSTR )
-
- #
- # Calculate the number of years between the two ET
- # representations, ~100.
- #
- print( 'Years between evaluations: {0:20.6f}'.\
- format( (et2 - et1)/spiceypy.jyear()))
-
- #
- # Reset the default year to 1969.
- #
- spiceypy.tsetyr( 1969 )
-
- #
- # Done. Unload the kernels.
- #
- spiceypy.kclear
-
-
- if __name__ == '__main__':
- xtic()
+.. py-editor::
+ :env: other
+
+ mk=r"""
+ KPL/MK
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/de405s.bsp',
+ 'kernels/pck/pck00008.tpc')
+ """
+ with open('xtic.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file corrd.tm')
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+
+ def xtic():
+
+ #
+ # Assign the META variable to the name of the meta-kernel
+ # that contains the LSK kernel and create an arbitrary
+ # time string.
+ #
+ CALSTR = 'Mar 15, 2003 12:34:56.789 AM PST'
+ META = 'xtic.tm'
+ AMBIGSTR = 'Mar 15, 79 12:34:56'
+ T_FORMAT1 = 'Wkd Mon DD HR:MN:SC PDT YYYY ::UTC-7'
+ T_FORMAT2 = 'Wkd Mon DD HR:MN ::UTC-7 YR (JULIAND.##### JDUTC)'
+
+ #
+ # Load the meta-kernel.
+ #
+ spiceypy.furnsh( META )
+ print( f'Original time string : f{CALSTR}' )
+
+ #
+ # Convert the time string to the number of ephemeris
+ # seconds past the J2000 epoch. This is the most common
+ # internal time representation used by the CSPICE
+ # system; CSPICE refers to this as ephemeris time (ET).
+ #
+ et = spiceypy.str2et( CALSTR )
+ print( f'Corresponding ET : {et:20.6f}\n' )
+
+ #
+ # Make a picture of an output format. Describe a Unix-like
+ # time string then send the picture and the 'et' value through
+ # spiceypy.timout to format and convert the ET representation
+ # of the time string into the form described in
+ # spiceypy.timout. The '::UTC-7' token indicates the time
+ # zone for the `timstr' output - PDT. 'PDT' is part of the
+ # output, but not a time system token.
+ #
+ timstr = spiceypy.timout( et, T_FORMAT1)
+ print( f'Time in string format 1 : {timstr}' )
+
+ timstr = spiceypy.timout( et, T_FORMAT2)
+ print( f'Time in string format 2 : {timstr}' )
+
+ #
+ # Why create a picture by hand when spiceypy can do it for
+ # you? Input a string to spiceypy.tpictr with the format of
+ # interest. `ok' returns a boolean indicating whether an
+ # error occurred while parsing the picture string, if so,
+ # an error diagnostic message returns in 'xerror'. In this
+ # example the picture string is known as correct.
+ #
+ pic = '12:34:56.789 P.M. PDT January 1, 2006'
+ [ pictr, ok, xerror] = spiceypy.tpictr(pic)
+
+ if not bool(ok):
+ print( xerror )
+ return
+
+
+ timstr = spiceypy.timout( et, pictr)
+ print( f'Time in string format 3 : {timstr}' )
+
+ #
+ # Two digit year representations often cause problems due to
+ # the ambiguity of the century. The routine spiceypy.tsetyr
+ # gives the user the ability to set a default range for 2
+ # digit year representation. SPICE uses 1969AD as the default
+ # start year so the numbers inclusive of 69 to 99 represent
+ # years 1969AD to 1999AD, the numbers inclusive of 00 to 68
+ # represent years 2000AD to 2068AD.
+ #
+ # The defined time string 'AMBIGSTR' contains a two-digit
+ # year. Since the SPICE base year is 1969, the time subsystem
+ # interprets the string as 1979.
+ #
+ et1 = spiceypy.str2et( AMBIGSTR )
+
+ #
+ # Set 1980 as the base year causes SPICE to interpret the
+ # time string's "79" as 2079.
+ #
+ spiceypy.tsetyr( 1980 )
+ et2 = spiceypy.str2et( AMBIGSTR )
+
+ #
+ # Calculate the number of years between the two ET
+ # representations, ~100.
+ #
+ years = (et2 - et1)/spiceypy.jyear()
+ print( f'Years between evaluations: {years:20.6f}' )
+
+ #
+ # Reset the default year to 1969.
+ #
+ spiceypy.tsetyr( 1969 )
+
+ #
+ # Done. Unload the kernels.
+ #
+ spiceypy.kclear()
+
+ if __name__ == '__main__':
+ xtic()
Run the code example
@@ -1460,98 +1489,68 @@ respond in an appropriate manner.
Code Solution
^^^^^^^^^^^^^
-.. code-block:: python
-
- from __future__ import print_function
- from builtins import input
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
- from spiceypy.utils.support_types import SpiceyError
-
- def aderr():
-
- #
- # Set initial parameters.
- #
- SPICETRUE = True
- SPICEFALSE= False
- doloop = SPICETRUE
-
- #
- # Load the data we need for state evaluation.
- #
- spiceypy.furnsh( 'aderr.tm' )
-
- #
- # Start our input query loop to the user.
- #
- while (doloop):
-
- #
- # For simplicity, we request only one input.
- # The program calculates the state vector from
- # Earth to the user specified target 'targ' in the
- # J2000 frame, at ephemeris time zero, using
- # aberration correction LT+S (light time plus
- # stellar aberration).
- #
- targ = input( 'Target: ' )
-
-
- if targ == 'NONE':
- #
- # An exit condition. If the user inputs NONE
- # for a target name, set the loop to stop...
- #
- doloop = SPICEFALSE
-
- else:
-
- #
- # ...otherwise evaluate the state between the Earth
- # and the target. Initialize an error handler.
- #
- try:
-
- #
- # Perform the state lookup.
- #
- [state, ltime] = spiceypy.spkezr(targ, 0., 'J2000',
- 'LT+S', 'EARTH')
-
- #
- # No error, output the state.
- #
- print( 'R : {0:20.6f} {1:20.6f} '
- '{2:20.5f}'.format(*state[0:3]))
- print( 'V : {0:20.6f} {1:20.6f} '
- '{2:20.6f}'.format(*state[3:6]) )
- print( 'LT: {0:20.6f}\n'.format(float(ltime)))
-
- except SpiceyError as err:
-
- #
- # What if spiceypy.spkezr signaled an error?
- # Then spiceypy signals an error to python.
- #
- # Examine the value of 'e' to retrieve the
- # error message.
- #
- print( err )
- print( )
-
-
- #
- # Done. Unload the kernels.
- #
- spiceypy.kclear
-
+.. py-editor::
+ :env: other
+
+ mk=r"""
+ KPL/MK
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/de405s.bsp',
+ 'kernels/pck/pck00008.tpc')
+ """
+ with open('aderr.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file aderr.tm')
+ print('')
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+
+ # For simplicity, we request only one input.
+ # The program calculates the state vector from
+ # Earth to the user specified target 'targ' in the
+ # J2000 frame, at ephemeris time zero, using
+ # aberration correction LT+S (light time plus
+ # stellar aberration).
+ def aderr(targ: str):
+ print(f'Target: {targ}')
+ spiceypy.furnsh( 'aderr.tm' )
+ try:
+ #
+ # Perform the state lookup.
+ #
+ state, ltime = spiceypy.spkezr(targ, 0.0, 'J2000', 'LT+S', 'EARTH')
+ #
+ # No error, output the state.
+ #
+ r0, r1, r2, v0, v1, v2 = state
+ print( f'R : {r0:20.6f} {r1:20.6f} {r2:20.6f}' )
+ print( f'V : {v0:20.6f} {v1:20.6f} {v2:20.6f}' )
+ print( f'LT: {ltime:20.6f}\n' )
+ except spiceypy.SpiceyError as err:
+ #
+ # What if spiceypy.spkezr signaled an error?
+ # Then spiceypy signals an error to python.
+ #
+ # Examine the value of 'err' to retrieve the
+ # error message.
+ #
+ print( err )
+ print( )
+ finally:
+ #
+ # Done. Unload the kernels.
+ #
+ spiceypy.kclear()
+
+ if __name__ == '__main__':
+ aderr('Moon')
+ aderr('Mars')
+ aderr('Pluto barycenter')
+ aderr('Puck')
- if __name__ == '__main__':
- aderr()
Run the code example
@@ -1564,6 +1563,7 @@ the velocity of the body in kilometers per second, and the 'LT' marker
identifies the one-way light time between the bodies at the requested
evaluation time.
+
.. code-block:: text
Target: Moon
@@ -1586,7 +1586,7 @@ evaluation time.
=====================================================================
===========
- Toolkit version: N0066
+ Toolkit version: N0067
SPICE(SPKINSUFFDATA) --
@@ -1613,6 +1613,12 @@ time 2000 JAN 01 12:00:00.000 (the requested time, ephemeris time zero).
Try another look-up, this time for “Casper”
+.. py-editor::
+ :env: other
+
+ aderr('Casper')
+
+
.. code-block:: text
Target: Casper
@@ -1641,6 +1647,12 @@ information on a body named 'Casper.'
Another look-up, this time, “Venus”.
+.. py-editor::
+ :env: other
+
+ aderr('Venus')
+
+
.. code-block:: text
Target: Venus
@@ -1710,145 +1722,156 @@ set of a number of time intervals.
Code Solution
^^^^^^^^^^^^^
-.. code-block:: python
-
- from __future__ import print_function
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
-
- def win():
-
- MAXSIZ = 8
-
- #
- # Define a set of time intervals. For the purposes of this
- # tutorial program, define time intervals representing
- # an unobscured line of sight between a ground station
- # and some body.
- #
- los = [ 'Jan 1, 2003 22:15:02', 'Jan 2, 2003 4:43:29',
- 'Jan 4, 2003 9:55:30', 'Jan 4, 2003 11:26:52',
- 'Jan 5, 2003 11:09:17', 'Jan 5, 2003 13:00:41',
- 'Jan 6, 2003 00:08:13', 'Jan 6, 2003 2:18:01' ]
-
- #
- # A second set of intervals representing the times for which
- # an acceptable phase angle exists between the ground station,
- # the body and the Sun.
- #
- phase = [ 'Jan 2, 2003 00:03:30', 'Jan 2, 2003 19:00:00',
- 'Jan 3, 2003 8:00:00', 'Jan 3, 2003 9:50:00',
- 'Jan 5, 2003 12:00:00', 'Jan 5, 2003 12:45:00',
- 'Jan 6, 2003 00:30:00', 'Jan 6, 2003 23:00:00' ]
-
- #
- # Load our meta kernel for the leapseconds data.
- #
- spiceypy.furnsh( 'win.tm' )
-
- #
- # SPICE windows consist of double precision values; convert
- # the string time tags defined in the 'los' and 'phase'
- # arrays to double precision ET. Store the double values
- # in the 'loswin' and 'phswin' windows.
- #
- los_et = spiceypy.str2et( los )
- phs_et = spiceypy.str2et( phase )
-
- loswin = spiceypy.stypes.SPICEDOUBLE_CELL( MAXSIZ )
- phswin = spiceypy.stypes.SPICEDOUBLE_CELL( MAXSIZ )
-
- for i in range(0, int( MAXSIZ/2 ) ):
- spiceypy.wninsd( los_et[2*i], los_et[2*i+1], loswin )
- spiceypy.wninsd( phs_et[2*i], phs_et[2*i+1], phswin )
-
- spiceypy.wnvald( MAXSIZ, MAXSIZ, loswin )
- spiceypy.wnvald( MAXSIZ, MAXSIZ, phswin )
-
- #
- # The issue for consideration, at what times do line of
- # sight events coincide with acceptable phase angles?
- # Perform the set operation AND on loswin, phswin,
- # (the intersection of the time intervals)
- # place the results in the window 'sched'.
- #
- sched = spiceypy.wnintd( loswin, phswin )
-
- print( 'Number data values in sched : '
- '{0:2d}'.format(spiceypy.card(sched)) )
-
- #
- # Output the results. The number of intervals in 'sched'
- # is half the number of data points (the cardinality).
- #
- print( ' ' )
- print( 'Time intervals meeting defined criterion.' )
-
- for i in range( spiceypy.card(sched)//2):
-
- #
- # Extract from the derived 'sched' the values defining the
- # time intervals.
- #
- [left, right ] = spiceypy.wnfetd( sched, i )
-
- #
- # Convert the ET values to UTC for human comprehension.
- #
- utcstr_l = spiceypy.et2utc( left , 'C', 3 )
- utcstr_r = spiceypy.et2utc( right, 'C', 3 )
-
- #
- # Output the UTC string and the corresponding index
- # for the interval.
- #
- print( '{0:2d} {1} {2}'.format(i, utcstr_l, utcstr_r))
-
-
- #
- # Summarize the 'sched' window.
- #
- [meas, avg, stddev, small, large] = spiceypy.wnsumd( sched )
-
- print( '\nSummary of sched window\n' )
-
- print( 'o Total measure of sched : {0:16.6f}'.format(meas))
- print( 'o Average measure of sched : {0:16.6f}'.format(avg))
- print( 'o Standard deviation of ' )
- print( ' the measures in sched : '
- '{0:16.6f}'.format(stddev))
-
- #
- # The values for small and large refer to the indexes of the
- # values in the window ('sched'). The shortest interval is
- #
- # [ sched.base[ sched.data + small]
- # sched.base[ sched.data + small +1] ];
- #
- # the longest is
- #
- # [ sched.base[ sched.data + large]
- # sched.base[ sched.data + large +1] ];
- #
- # Output the interval indexes for the shortest and longest
- # intervals. As Python bases an array index on 0, the interval
- # index is half the array index.
- #
- print( 'o Index of shortest interval: '
- '{0:2d}'.format(int(small/2)) )
- print( 'o Index of longest interval : '
- '{0:2d}'.format(int(large/2)) )
-
- #
- # Done. Unload the kernels.
- #
- spiceypy.kclear
-
- if __name__ == '__main__':
- win()
+.. py-editor::
+ :env: other
+
+ mk=r"""
+ KPL/MK
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/de405s.bsp',
+ 'kernels/pck/pck00008.tpc')
+ """
+ with open('win.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file win.tm')
+ print('')
+
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+
+ def win():
+
+ MAXSIZ = 8
+
+ #
+ # Define a set of time intervals. For the purposes of this
+ # tutorial program, define time intervals representing
+ # an unobscured line of sight between a ground station
+ # and some body.
+ #
+ los = [ 'Jan 1, 2003 22:15:02', 'Jan 2, 2003 4:43:29',
+ 'Jan 4, 2003 9:55:30', 'Jan 4, 2003 11:26:52',
+ 'Jan 5, 2003 11:09:17', 'Jan 5, 2003 13:00:41',
+ 'Jan 6, 2003 00:08:13', 'Jan 6, 2003 2:18:01' ]
+
+ #
+ # A second set of intervals representing the times for which
+ # an acceptable phase angle exists between the ground station,
+ # the body and the Sun.
+ #
+ phase = [ 'Jan 2, 2003 00:03:30', 'Jan 2, 2003 19:00:00',
+ 'Jan 3, 2003 8:00:00', 'Jan 3, 2003 9:50:00',
+ 'Jan 5, 2003 12:00:00', 'Jan 5, 2003 12:45:00',
+ 'Jan 6, 2003 00:30:00', 'Jan 6, 2003 23:00:00' ]
+
+ #
+ # Load our meta kernel for the leapseconds data.
+ #
+ spiceypy.furnsh( 'win.tm' )
+
+ #
+ # SPICE windows consist of double precision values; convert
+ # the string time tags defined in the 'los' and 'phase'
+ # arrays to double precision ET. Store the double values
+ # in the 'loswin' and 'phswin' windows.
+ #
+ los_et = spiceypy.str2et( los )
+ phs_et = spiceypy.str2et( phase )
+
+ loswin = spiceypy.stypes.SPICEDOUBLE_CELL( MAXSIZ )
+ phswin = spiceypy.stypes.SPICEDOUBLE_CELL( MAXSIZ )
+
+ for i in range(0, int( MAXSIZ/2 ) ):
+ spiceypy.wninsd( los_et[2*i], los_et[2*i+1], loswin )
+ spiceypy.wninsd( phs_et[2*i], phs_et[2*i+1], phswin )
+
+ spiceypy.wnvald( MAXSIZ, MAXSIZ, loswin )
+ spiceypy.wnvald( MAXSIZ, MAXSIZ, phswin )
+
+ #
+ # The issue for consideration, at what times do line of
+ # sight events coincide with acceptable phase angles?
+ # Perform the set operation AND on loswin, phswin,
+ # (the intersection of the time intervals)
+ # place the results in the window 'sched'.
+ #
+ sched = spiceypy.wnintd( loswin, phswin )
+
+ print( 'Number data values in sched : '
+ '{0:2d}'.format(spiceypy.card(sched)) )
+
+ #
+ # Output the results. The number of intervals in 'sched'
+ # is half the number of data points (the cardinality).
+ #
+ print( ' ' )
+ print( 'Time intervals meeting defined criterion.' )
+
+ for i in range( spiceypy.card(sched)//2):
+
+ #
+ # Extract from the derived 'sched' the values defining the
+ # time intervals.
+ #
+ [left, right ] = spiceypy.wnfetd( sched, i )
+
+ #
+ # Convert the ET values to UTC for human comprehension.
+ #
+ utcstr_l = spiceypy.et2utc( left , 'C', 3 )
+ utcstr_r = spiceypy.et2utc( right, 'C', 3 )
+
+ #
+ # Output the UTC string and the corresponding index
+ # for the interval.
+ #
+ print( '{0:2d} {1} {2}'.format(i, utcstr_l, utcstr_r))
+
+
+ #
+ # Summarize the 'sched' window.
+ #
+ [meas, avg, stddev, small, large] = spiceypy.wnsumd( sched )
+
+ print( '\nSummary of sched window\n' )
+
+ print( 'o Total measure of sched : {0:16.6f}'.format(meas))
+ print( 'o Average measure of sched : {0:16.6f}'.format(avg))
+ print( 'o Standard deviation of ' )
+ print( ' the measures in sched : '
+ '{0:16.6f}'.format(stddev))
+
+ #
+ # The values for small and large refer to the indexes of the
+ # values in the window ('sched'). The shortest interval is
+ #
+ # [ sched.base[ sched.data + small]
+ # sched.base[ sched.data + small +1] ];
+ #
+ # the longest is
+ #
+ # [ sched.base[ sched.data + large]
+ # sched.base[ sched.data + large +1] ];
+ #
+ # Output the interval indexes for the shortest and longest
+ # intervals. As Python bases an array index on 0, the interval
+ # index is half the array index.
+ #
+ print( 'o Index of shortest interval: '
+ '{0:2d}'.format(int(small/2)) )
+ print( 'o Index of longest interval : '
+ '{0:2d}'.format(int(large/2)) )
+
+ #
+ # Done. Unload the kernels.
+ #
+ spiceypy.kclear()
+
+ if __name__ == '__main__':
+ win()
Run the code example
@@ -1950,106 +1973,58 @@ often used in astrodynamics, time calculations, and geometry.
Code Solution
^^^^^^^^^^^^^
-.. code-block:: python
-
- from __future__ import print_function
- from builtins import input
-
- #
- # Import the CSPICE-Python interface.
- #
- import spiceypy
-
-
- def tostan(alias):
-
- value = alias
-
- #
- # As a convenience, let's alias a few common terms
- # to their appropriate counterpart.
- #
- if alias == 'meter':
-
- #
- # First, a 'meter' by any other name is a
- # 'METER' and smells as sweet ...
- #
- value = 'METERS'
-
- elif (alias == 'klicks') \
- or (alias == 'kilometers') \
- or (alias =='kilometer'):
-
- #
- # ... 'klicks' and 'KILOMETERS' and 'KILOMETER'
- # identifies 'KM'....
- #
- value = 'KM'
-
- elif alias == 'secs':
-
- #
- # ... 'secs' to 'SECONDS'.
- #
- value = 'SECONDS'
-
- elif alias == 'miles':
-
- #
- # ... and finally 'miles' to 'STATUTE_MILES'.
- # Normal people think in statute miles.
- # Only sailors think in nautical miles - one
- # minute of arc at the equator.
- #
- value = 'STATUTE_MILES'
+.. py-editor::
+ :env: other
+
+ #
+ # Import the CSPICE-Python interface.
+ #
+ import spiceypy
+
+ aliases = {
+ 'meter': 'METER',
+ 'klicks': 'KM',
+ 'kilometers': 'KM',
+ 'kilometer': 'KM',
+ 'secs': 'SECONDS',
+ 'miles': 'STATUTE_MILES'
+ }
+
+ def tostan(alias):
+ return aliases.get(alias, alias)
+
+ def units(funits, fvalue, tunits):
+ #
+ # Display the Toolkit version string with a spiceypy.tkvrsn
+ # call.
+ #
+ vers = spiceypy.tkvrsn( 'TOOLKIT' )
+ print('\nConvert demo program compiled against CSPICE Toolkit ' + vers)
+ #
+ # The user first inputs the name of a unit of measure.
+ # Send the name through tostan for de-aliasing.
+ #
+ print(f'From Units : {funits}')
+ funits = tostan( funits )
+ #
+ # Input a double precision value to express in a new
+ # unit format.
+ #
+ print(f'From Value : {fvalue}')
+ #
+ # Now the user inputs the name of the output units.
+ # Again we send the units name through tostan for
+ # de-aliasing.
+ #
+ print(f'To Units : {tunits}')
+ tunits = tostan( tunits )
+ tvalue = spiceypy.convrt( fvalue, funits, tunits)
+ print( '{0:12.5f} {1}'.format(tvalue, tunits) )
+
+ if __name__ == '__main__':
+ units('klicks', 3, 'miles')
+ units('miles', 26.2, 'km')
- else:
- pass
-
-
- #
- # Much better. Now return. If the input matched
- # none of the aliases, this function did nothing.
- #
- return value
-
- def units():
-
- #
- # Display the Toolkit version string with a spiceypy.tkvrsn
- # call.
- #
- vers = spiceypy.tkvrsn( 'TOOLKIT' )
- print('\nConvert demo program compiled against CSPICE '
- 'Toolkit ' + vers)
-
- #
- # The user first inputs the name of a unit of measure.
- # Send the name through tostan for de-aliasing.
- #
- funits = input( 'From Units : ' )
- funits = tostan( funits )
-
- #
- # Input a double precision value to express in a new
- # unit format.
- #
- fvalue = float(input( 'From Value : ' ))
-
- #
- # Now the user inputs the name of the output units.
- # Again we send the units name through tostan for
- # de-aliasing.
- #
- tunits = input( 'To Units : ' )
- tunits = tostan( tunits )
-
- tvalue = spiceypy.convrt( fvalue, funits, tunits)
- print( '{0:12.5f} {1}'.format(tvalue, tunits) )
-
- if __name__ == '__main__':
- units()
Run the code example
@@ -2092,9 +2067,8 @@ calculate some rudimentary values.
Code Solution
^^^^^^^^^^^^^
-.. code-block:: python
-
- from __future__ import print_function
+.. py-editor::
+ :env: other
#
# Import the CSPICE-Python interface.
diff --git a/docs/pyscript_editor.py b/docs/pyscript_editor.py
index 47d9e12c..21549e7d 100644
--- a/docs/pyscript_editor.py
+++ b/docs/pyscript_editor.py
@@ -5,16 +5,24 @@
wrapped in the same ``div.highlight.highlight-python`` structure that
Sphinx/Pygments produces, so it inherits your theme's code-block styling.
+The hidden ```` inside each editor div is compatible with
+``sphinx_copybutton``: that extension's JS selector finds ``div.highlight pre``
+and wires a clipboard copy button automatically.
+
Usage in conf.py
----------------
extensions = [..., "pyscript_editor"]
# Optional global defaults (all overridable per-directive):
- pyscript_version = "2026.2.1" # PyScript release
- pyscript_env = "shared" # py-editor env attribute
- pyscript_config = "pyscript.json" # py-editor config attribute
- pyscript_mini_coi = "mini-coi.js" # path to mini-coi shim;
- # set to "" to skip
+ pyscript_version = "2026.2.1" # PyScript release tag
+ pyscript_env = "shared" # default py-editor env name
+ pyscript_config = "pyscript.json" # default PyScript config file;
+ # set to "" to omit
+ pyscript_mini_coi = "mini-coi.js" # path to mini-coi shim;
+ # set to "" to skip
+ pyscript_hide_gutters = True # hide CodeMirror line-number gutters
+ pyscript_hide_env_label = True # hide the "pyodide-" label
+ # rendered above each editor box
Usage in .rst files
-------------------
@@ -28,17 +36,40 @@
Override any option per block::
.. py-editor::
- :env: isolated
- :config: other.json
+ :env: myenv
+ :config: my_pyscript.json
print("hello")
-The ``mini-coi.js`` script and the PyScript stylesheet/module are injected
-only once per page, no matter how many ``.. py-editor::`` directives appear.
+Directive options
+-----------------
+:env: PyScript environment name (``env=`` attribute on ``')
@@ -96,6 +134,8 @@ def _head_html(mini_coi: str, version: str, hide_gutters: bool) -> str:
f''
)
+ if hide_env_label:
+ parts.append(_HIDE_ENV_LABEL_CSS)
if hide_gutters:
parts.append(_HIDE_GUTTERS_JS)
return "\n".join(parts) + "\n"
@@ -115,6 +155,7 @@ class PyEditorDirective(Directive):
"env": directives.unchanged,
"config": directives.unchanged,
"target": directives.unchanged,
+ "setup": directives.flag,
}
def run(self) -> list[nodes.Node]:
@@ -125,25 +166,37 @@ def run(self) -> list[nodes.Node]:
version = cfg.pyscript_version
mini_coi = cfg.pyscript_mini_coi
hide_gutters = cfg.pyscript_hide_gutters
+ hide_env_label = cfg.pyscript_hide_env_label
ed_env = self.options.get("env", cfg.pyscript_env)
ed_cfg = self.options.get("config", cfg.pyscript_config)
ed_target = self.options.get("target", None)
+ ed_setup = "setup" in self.options
result: list[nodes.Node] = []
# ---- inject assets once per document ----
injected = getattr(env, _HEAD_KEY, set())
if env.docname not in injected:
- result.append(_raw(_head_html(mini_coi, version, hide_gutters)))
+ result.append(_raw(_head_html(mini_coi, version, hide_gutters, hide_env_label)))
injected.add(env.docname)
setattr(env, _HEAD_KEY, injected)
- # ---- emit config attr only on the first editor for each (page, env) ----
- # PyScript reads the config once per named environment; repeating it is harmless
- # but emitting it only on the first occurrence keeps the HTML clean.
+ # ---- emit config= exactly once per (page, env) pair ----
+ # Setup blocks own config for their env and must declare it explicitly.
+ # Regular blocks get config= only if no setup block has claimed the env.
+ if ed_setup and "config" not in self.options:
+ raise self.error(":setup: requires :config: to be explicitly specified")
+
+ setup_envs = getattr(env, _SETUP_ENV_KEY, set())
env_configs = getattr(env, _ENV_KEY, set())
env_key = (env.docname, ed_env)
- if env_key not in env_configs:
+ if ed_setup:
+ config_part = f' config="{ed_cfg}"'
+ setup_envs.add(env_key)
+ env_configs.add(env_key)
+ setattr(env, _SETUP_ENV_KEY, setup_envs)
+ setattr(env, _ENV_KEY, env_configs)
+ elif env_key not in setup_envs and env_key not in env_configs:
config_part = f' config="{ed_cfg}"' if ed_cfg else ""
env_configs.add(env_key)
setattr(env, _ENV_KEY, env_configs)
@@ -154,10 +207,23 @@ def run(self) -> list[nodes.Node]:
code = "\n".join(self.content)
indented = "\n".join(" " + line for line in code.splitlines())
+ # Assign a unique ID so sphinx_copybutton can target the hidden .
+ # The counter is global across all documents in a build (matching how
+ # Sphinx/Pygments numbers codecell0, codecell1, …).
+ cell_num = getattr(env, _CELL_COUNTER_KEY, 0)
+ cell_id = f"pyscript-codecell{cell_num}"
+ setattr(env, _CELL_COUNTER_KEY, cell_num + 1)
+
+ # sphinx_copybutton looks for `div.highlight pre` and wires a copy
+ # button to it via data-clipboard-target. The is hidden
+ # visually; clipboard.js reads textContent regardless of visibility.
+ escaped_code = html_mod.escape(code)
+
editor_html = (
'\n'
'\n'
- f'\n"
"\n"
@@ -181,6 +247,7 @@ def setup(app: Sphinx) -> dict:
app.add_config_value("pyscript_config", "pyscript.json", "html")
app.add_config_value("pyscript_mini_coi", "mini-coi.js", "html")
app.add_config_value("pyscript_hide_gutters", True, "html")
+ app.add_config_value("pyscript_hide_env_label", True, "html")
app.add_directive("py-editor", PyEditorDirective)
diff --git a/docs/pyscript_min.json b/docs/pyscript_min.json
new file mode 100644
index 00000000..31d46a8b
--- /dev/null
+++ b/docs/pyscript_min.json
@@ -0,0 +1,6 @@
+{
+ "packages": [
+ "numpy",
+ "https://cdn.jsdelivr.net/gh/AndrewAnnex/spiceypy-wheels-dist@v8.0.2-dev.2/spiceypy-8.0.2-cp313-cp313-pyodide_2025_0_wasm32.whl"
+ ]
+}
diff --git a/docs/pyscript_other_stuff.json b/docs/pyscript_other_stuff.json
new file mode 100644
index 00000000..8f6d7b93
--- /dev/null
+++ b/docs/pyscript_other_stuff.json
@@ -0,0 +1,11 @@
+{
+ "packages": [
+ "numpy",
+ "https://cdn.jsdelivr.net/gh/AndrewAnnex/spiceypy-wheels-dist@v8.0.2-dev.2/spiceypy-8.0.2-cp313-cp313-pyodide_2025_0_wasm32.whl"
+ ],
+ "files": {
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/naif0008.tls": "./kernels/lsk/naif0008.tls",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/de405s.bsp": "./kernels/spk/de405s.bsp",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/pck00008.tpc": "./kernels/pck/pck00008.tpc"
+ }
+}
diff --git a/docs/remote_sensing.rst b/docs/remote_sensing.rst
index e860e6b9..6f6d83a4 100644
--- a/docs/remote_sensing.rst
+++ b/docs/remote_sensing.rst
@@ -102,7 +102,9 @@ built-in Python help system.
For example, the Python help function
-.. code-block:: python
+.. py-editor::
+ :env: tmp
+ :config: pyscript_min.json
import spiceypy
help(spiceypy.str2et)
@@ -2101,7 +2103,7 @@ A sample solution to the problem follows:
.. code-block:: python
:linenos:
-
+
#
# Solution fovint.py
#
From 2bfb2c2b92a46bf3bb44d1a679b7a1bad7ff276c Mon Sep 17 00:00:00 2001
From: Andrew Annex <2126916+AndrewAnnex@users.noreply.github.com>
Date: Sun, 8 Mar 2026 17:38:25 -0700
Subject: [PATCH 03/10] additional fix, seems like blank lines are a problem
sometimes?
---
docs/other_stuff.rst | 47 +++++++++++++++++---------------------------
1 file changed, 18 insertions(+), 29 deletions(-)
diff --git a/docs/other_stuff.rst b/docs/other_stuff.rst
index ed36c2f3..7f7ae6bc 100644
--- a/docs/other_stuff.rst
+++ b/docs/other_stuff.rst
@@ -784,7 +784,6 @@ Code Solution
# Import the CSPICE-Python interface.
#
import spiceypy
- from spiceypy.utils.support_types import SpiceyError
def kervar():
@@ -832,24 +831,22 @@ Code Solution
cvals = spiceypy.gnpool( tmplate, START, N_ITEMS )
print( 'Number variables matching template: {0}'.\
format( len(cvals)) )
- except SpiceyError:
+ except spiceypy.SpiceyError:
print( 'No kernel variables matched template.' )
return
-
#
# Okay, now we know something about the kernel pool
# variables of interest to us. Let's find out more...
#
for cval in cvals:
-
#
# Use spiceypy.dtpool to return the dimension and type,
# C (character) or N (numeric), of each pool
# variable name in the cvals array. We know the
# kernel data exists.
#
- [dim, type] = spiceypy.dtpool( cval )
+ dim, type = spiceypy.dtpool( cval )
print( '\n' + cval )
print( ' Number items: {0} Of type: {1}\n'.\
@@ -859,7 +856,6 @@ Code Solution
# Test character equality, 'N' or 'C'.
#
if type == 'N':
-
#
# If 'type' equals 'N', we found a numeric array.
# In this case any numeric array will be an array
@@ -870,27 +866,20 @@ Code Solution
dvars = spiceypy.gdpool( cval, START, N_ITEMS )
for dvar in dvars:
print(' Numeric value: {0:20.6f}'.format(dvar))
-
elif type == 'C':
-
#
# If 'type' equals 'C', we found a string array.
# spiceypy.gcpool retrieves string values from the
# kernel pool.
#
cvars = spiceypy.gcpool( cval, START, N_ITEMS )
-
for cvar in cvars:
print(' String value: {0}\n'.format(cvar))
-
else:
-
#
# This block should never execute.
#
print('Unknown type. Code error.')
-
-
#
# Now look at the time variable EXAMPLE_TIMES. Extract this
# value as an array of doubles.
@@ -1507,7 +1496,7 @@ Code Solution
# Import the CSPICE-Python interface.
#
import spiceypy
-
+
# For simplicity, we request only one input.
# The program calculates the state vector from
# Earth to the user specified target 'targ' in the
@@ -1544,7 +1533,7 @@ Code Solution
# Done. Unload the kernels.
#
spiceypy.kclear()
-
+
if __name__ == '__main__':
aderr('Moon')
aderr('Mars')
@@ -1626,7 +1615,7 @@ Try another look-up, this time for “Casper”
=====================================================================
===========
- Toolkit version: N0066
+ Toolkit version: N0067
SPICE(IDCODENOTFOUND) --
@@ -1724,7 +1713,7 @@ Code Solution
.. py-editor::
:env: other
-
+
mk=r"""
KPL/MK
\begindata
@@ -1736,7 +1725,7 @@ Code Solution
dst.write(mk)
print('Wrote kernel file win.tm')
print('')
-
+
#
# Import the CSPICE-Python interface.
#
@@ -1980,7 +1969,7 @@ Code Solution
# Import the CSPICE-Python interface.
#
import spiceypy
-
+
aliases = {
'meter': 'METER',
'klicks': 'KM',
@@ -1989,38 +1978,38 @@ Code Solution
'secs': 'SECONDS',
'miles': 'STATUTE_MILES'
}
-
+
def tostan(alias):
return aliases.get(alias, alias)
-
+
def units(funits, fvalue, tunits):
#
# Display the Toolkit version string with a spiceypy.tkvrsn
# call.
#
vers = spiceypy.tkvrsn( 'TOOLKIT' )
- print('\nConvert demo program compiled against CSPICE Toolkit ' + vers)
+ print('\nConvert demo program compiled against CSPICE Toolkit ' + vers)
#
# The user first inputs the name of a unit of measure.
# Send the name through tostan for de-aliasing.
#
print(f'From Units : {funits}')
- funits = tostan( funits )
+ funits = tostan( funits )
#
# Input a double precision value to express in a new
# unit format.
#
- print(f'From Value : {fvalue}')
+ print(f'From Value : {fvalue}')
#
# Now the user inputs the name of the output units.
# Again we send the units name through tostan for
# de-aliasing.
#
print(f'To Units : {tunits}')
- tunits = tostan( tunits )
+ tunits = tostan( tunits )
tvalue = spiceypy.convrt( fvalue, funits, tunits)
print( '{0:12.5f} {1}'.format(tvalue, tunits) )
-
+
if __name__ == '__main__':
units('klicks', 3, 'miles')
units('miles', 26.2, 'km')
@@ -2034,7 +2023,7 @@ was linked:
.. code-block:: text
- Convert demo program compiled against CSPICE Toolkit CSPICE_N0066
+ Convert demo program compiled against CSPICE Toolkit CSPICE_N0067
From Units : klicks
From Value : 3
To Units : miles
@@ -2046,9 +2035,9 @@ Legend states Pheidippides ran from the Marathon Plain to Athens. The
modern marathon race (inspired by this event) spans 26.2 miles. How far
in kilometers?
-::
+.. code-block:: text
- Convert demo program compiled against CSPICE Toolkit CSPICE_N0066
+ Convert demo program compiled against CSPICE Toolkit CSPICE_N0067
From Units : miles
From Value : 26.2
To Units : km
From 3434529cd7beaa0465d8efe4de7eb0950ddb81c3 Mon Sep 17 00:00:00 2001
From: Andrew Annex <2126916+AndrewAnnex@users.noreply.github.com>
Date: Sun, 8 Mar 2026 22:37:47 -0700
Subject: [PATCH 04/10] seemingly working remote sensing code and
simplification, will need to verify correctness and also fix just lots of bad
formatting choices
---
docs/basics.rst | 6 +-
docs/binary_pck.rst | 4 +-
docs/conf.py | 1 +
docs/pyscript_remote_sensing.json | 19 +
docs/remote_sensing.rst | 1225 ++++++++++-------------------
5 files changed, 454 insertions(+), 801 deletions(-)
create mode 100644 docs/pyscript_remote_sensing.json
diff --git a/docs/basics.rst b/docs/basics.rst
index 7872aa2e..94b44850 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -41,7 +41,9 @@ A simple example program
The following calls the SPICE function :py:meth:`spiceypy.spiceypy.tkvrsn` which outputs the version
of cspice that SpiceyPy is wrapping.
-.. code:: python
+.. py-editor::
+ :env: other
+ :config: pyscript_min.json
import spiceypy as spice
@@ -51,5 +53,5 @@ This should output the following string:
.. parsed-literal::
- 'CSPICE_N0066'
+ 'CSPICE_N0067'
diff --git a/docs/binary_pck.rst b/docs/binary_pck.rst
index fb8f662c..a9f0bdc7 100644
--- a/docs/binary_pck.rst
+++ b/docs/binary_pck.rst
@@ -78,7 +78,9 @@ built-in Python help system.
For example, the Python help function
-.. code-block:: python
+.. py-editor::
+ :env: other
+ :config: pyscript_min.json
import spiceypy
diff --git a/docs/conf.py b/docs/conf.py
index 0697b1ad..0523fc19 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -219,6 +219,7 @@
html_extra_path = [
"pyscript.json",
"pyscript_min.json",
+ "pyscript_remote_sensing.json",
"pyscript_other_stuff.json",
"mini-coi.js",
]
diff --git a/docs/pyscript_remote_sensing.json b/docs/pyscript_remote_sensing.json
new file mode 100644
index 00000000..3f6c8cfe
--- /dev/null
+++ b/docs/pyscript_remote_sensing.json
@@ -0,0 +1,19 @@
+{
+ "packages": [
+ "numpy",
+ "https://cdn.jsdelivr.net/gh/AndrewAnnex/spiceypy-wheels-dist@v8.0.2-dev.2/spiceypy-8.0.2-cp313-cp313-pyodide_2025_0_wasm32.whl"
+ ],
+ "files": {
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/naif0008.tls": "kernels/lsk/naif0008.tls",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/cas00084.tsc": "kernels/sclk/cas00084.tsc",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/981005_PLTEPH-DE405S.bsp": "kernels/spk/981005_PLTEPH-DE405S.bsp",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/020514_SE_SAT105.bsp": "kernels/spk/020514_SE_SAT105.bsp",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/030201AP_SK_SM546_T45.bsp": "kernels/spk/030201AP_SK_SM546_T45.bsp",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/cas_v37.tf": "kernels/fk/cas_v37.tf",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/04135_04171pc_psiv2.bc": "kernels/ck/04135_04171pc_psiv2.bc",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/cpck05Mar2004.tpc": "kernels/pck/cpck05Mar2004.tpc",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/cas_iss_v09.ti": "kernels/ik/cas_iss_v09.ti",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/phoebe_64q.bds": "kernels/dsk/phoebe_64q.bds",
+ "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/jup310_2004.bsp": "kernels/spk/jup310_2004.bsp"
+ }
+}
diff --git a/docs/remote_sensing.rst b/docs/remote_sensing.rst
index 6f6d83a4..9af959fb 100644
--- a/docs/remote_sensing.rst
+++ b/docs/remote_sensing.rst
@@ -103,8 +103,15 @@ built-in Python help system.
For example, the Python help function
.. py-editor::
- :env: tmp
- :config: pyscript_min.json
+ :env: rsenv
+ :config: pyscript_remote_sensing.json
+ :setup:
+
+ import spiceypy
+
+.. py-editor::
+ :env: rsenv
+ :config: pyscript_remote_sensing.json
import spiceypy
help(spiceypy.str2et)
@@ -293,8 +300,11 @@ Solution Meta-Kernel
The meta-kernel we created for the solution to this exercise is named
'convtm.tm'. Its contents follow:
-.. code-block:: text
+.. py-editor::
+ :env: rsenv
+ :config: pyscript_remote_sensing.json
+ mk = r"""
KPL/MK
This is the meta-kernel used in the solution of the "Time
@@ -313,73 +323,45 @@ The meta-kernel we created for the solution to this exercise is named
KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
'kernels/sclk/cas00084.tsc' )
\begintext
+ """
+ with open('convtm.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file convtm.tm')
Solution Source Code
A sample solution to the problem follows:
-.. code-block:: python
- :linenos:
-
- #
- # Solution convtm
- #
- from __future__ import print_function
- from builtins import input
-
- import spiceypy
-
- def convtm():
- #
- # Local Parameters
- #
- METAKR = 'convtm.tm'
- SCLKID = -82
-
- spiceypy.furnsh( METAKR )
-
- #
- # Prompt the user for the input time string.
- #
- utctim = input( 'Input UTC Time: ' )
-
- print( 'Converting UTC Time: {:s}'.format( utctim ) )
-
- #
- # Convert utctim to ET.
- #
- et = spiceypy.str2et( utctim )
-
- print( ' ET Seconds Past J2000: {:16.3f}'.format( et ) )
-
- #
- # Now convert ET to a calendar time string.
- # This can be accomplished in two ways.
- #
- calet = spiceypy.etcal( et )
-
- print( ' Calendar ET (etcal): {:s}'.format( calet ) )
-
- #
- # Or use timout for finer control over the
- # output format. The picture below was built
- # by examining the header of timout.
- #
- calet = spiceypy.timout( et, 'YYYY-MON-DDTHR:MN:SC ::TDB' )
-
- print( ' Calendar ET (timout): {:s}'.format( calet ) )
-
- #
- # Convert ET to spacecraft clock time.
- #
- sclkst = spiceypy.sce2s( SCLKID, et )
-
- print( ' Spacecraft Clock Time: {:s}'.format( sclkst ) )
-
- spiceypy.unload( METAKR )
-
- if __name__ == '__main__':
- convtm()
+.. py-editor::
+ :env: rsenv
+
+ # Solution convtm
+ import spiceypy
+
+ def convtm(utctim='2004 jun 11 19:32:00'):
+ METAKR = 'convtm.tm'
+ SCLKID = -82
+
+ spiceypy.furnsh(METAKR)
+ print(f'Converting UTC Time: {utctim}')
+ et = spiceypy.str2et(utctim)
+ print(f' ET Seconds Past J2000: {et:16.3f}')
+
+ # Convert ET to a calendar time string; this can be done two ways.
+ calet = spiceypy.etcal(et)
+ print(f' Calendar ET (etcal): {calet}')
+
+ # Or use timout for finer control over the output format.
+ # The picture below was built by examining the header of timout.
+ calet = spiceypy.timout(et, 'YYYY-MON-DDTHR:MN:SC ::TDB')
+ print(f' Calendar ET (timout): {calet}')
+
+ # Convert ET to spacecraft clock time.
+ sclkst = spiceypy.sce2s(SCLKID, et)
+ print(f' Spacecraft Clock Time: {sclkst}')
+ spiceypy.unload(METAKR)
+
+ convtm()
Solution Sample Output
@@ -673,163 +655,102 @@ Solution Meta-Kernel
The meta-kernel we created for the solution to this exercise is named
'getsta.tm'. Its contents follow:
-.. code-block:: text
+.. py-editor::
+ :env: rsenv
- KPL/MK
+ mk = r"""
+ KPL/MK
- This is the meta-kernel used in the solution of the
- "Obtaining Target States and Positions" task in the
- Remote Sensing Hands On Lesson.
+ This is the meta-kernel used in the solution of the
+ "Obtaining Target States and Positions" task in the
+ Remote Sensing Hands On Lesson.
- The names and contents of the kernels referenced by this
- meta-kernel are as follows:
+ The names and contents of the kernels referenced by this
+ meta-kernel are as follows:
- File name Contents
- -------------------------- -----------------------------
- naif0008.tls Generic LSK
- 981005_PLTEPH-DE405S.bsp Solar System Ephemeris
- 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris
- 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK
+ File name Contents
+ -------------------------- -----------------------------
+ naif0008.tls Generic LSK
+ 981005_PLTEPH-DE405S.bsp Solar System Ephemeris
+ 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris
+ 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK
- \begindata
- KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
- 'kernels/spk/981005_PLTEPH-DE405S.bsp',
- 'kernels/spk/020514_SE_SAT105.bsp',
- 'kernels/spk/030201AP_SK_SM546_T45.bsp' )
- \begintext
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/981005_PLTEPH-DE405S.bsp',
+ 'kernels/spk/020514_SE_SAT105.bsp',
+ 'kernels/spk/030201AP_SK_SM546_T45.bsp' )
+ \begintext
+ """
+ with open('getsta.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file getsta.tm')
Solution Source Code
A sample solution to the problem follows:
-.. code-block:: python
- :linenos:
-
- #
- # Solution getsta.py
- #
- from __future__ import print_function
- from builtins import input
-
- import spiceypy
-
- def getsta():
- #
- # Local parameters
- #
- METAKR = 'getsta.tm'
-
- #
- # Load the kernels that this program requires. We
- # will need a leapseconds kernel to convert input
- # UTC time strings into ET. We also will need the
- # necessary SPK files with coverage for the bodies
- # in which we are interested.
- #
- spiceypy.furnsh( METAKR )
-
- #
- #Prompt the user for the input time string.
- #
- utctim = input( 'Input UTC Time: ' )
-
- print( 'Converting UTC Time: {:s}'.format(utctim) )
-
- #
- #Convert utctim to ET.
- #
- et = spiceypy.str2et( utctim )
-
- print( ' ET seconds past J2000: {:16.3f}'.format(et) )
-
- #
- # Compute the apparent state of Phoebe as seen from
- # CASSINI in the J2000 frame. All of the ephemeris
- # readers return states in units of kilometers and
- # kilometers per second.
- #
- [state, ltime] = spiceypy.spkezr( 'PHOEBE', et, 'J2000',
- 'LT+S', 'CASSINI' )
-
- print( ' Apparent state of Phoebe as seen '
- 'from CASSINI in the J2000\n'
- ' frame (km, km/s):' )
-
- print( ' X = {:16.3f}'.format(state[0]) )
- print( ' Y = {:16.3f}'.format(state[1]) )
- print( ' Z = {:16.3f}'.format(state[2]) )
- print( ' VX = {:16.3f}'.format(state[3]) )
- print( ' VY = {:16.3f}'.format(state[4]) )
- print( ' VZ = {:16.3f}'.format(state[5]) )
-
- #
- # Compute the apparent position of Earth as seen from
- # CASSINI in the J2000 frame. Note: We could have
- # continued using spkezr and simply ignored the
- # velocity components.
- #
- [pos, ltime] = spiceypy.spkpos( 'EARTH', et, 'J2000',
- 'LT+S', 'CASSINI', )
-
- print( ' Apparent position of Earth as '
- 'seen from CASSINI in the J2000\n'
- ' frame (km):' )
- print( ' X = {:16.3f}'.format(pos[0]) )
- print( ' Y = {:16.3f}'.format(pos[1]) )
- print( ' Z = {:16.3f}'.format(pos[2]) )
-
- #
- # We need only display LTIME, as it is precisely the
- # light time in which we are interested.
- #
- print( ' One way light time between CASSINI and '
- 'the apparent position\n'
- ' of Earth (seconds):'
- ' {:16.3f}'.format(ltime) )
-
- #
- # Compute the apparent position of the Sun as seen from
- # PHOEBE in the J2000 frame.
- #
- [pos, ltime] = spiceypy.spkpos( 'SUN', et, 'J2000',
- 'LT+S', 'PHOEBE', )
-
- print( ' Apparent position of Sun as '
- 'seen from Phoebe in the\n'
- ' J2000 frame (km):' )
- print( ' X = {:16.3f}'.format(pos[0]) )
- print( ' Y = {:16.3f}'.format(pos[1]) )
- print( ' Z = {:16.3f}'.format(pos[2]) )
-
- #
- # Now we need to compute the actual distance between
- # the Sun and Phoebe. The above spkpos call gives us
- # the apparent distance, so we need to adjust our
- # aberration correction appropriately.
- #
- [pos, ltime] = spiceypy.spkpos( 'SUN', et, 'J2000',
- 'NONE', 'PHOEBE' )
-
- #
- # Compute the distance between the body centers in
- # kilometers.
- #
- dist = spiceypy.vnorm( pos )
-
- #
- # Convert this value to AU using convrt.
- #
- dist = spiceypy.convrt( dist, 'KM', 'AU' )
-
- print( ' Actual distance between Sun and '
- 'Phoebe body centers:\n'
- ' (AU): {:16.3f}'.format(dist) )
-
- spiceypy.unload( METAKR )
-
- if __name__ == '__main__':
- getsta()
+.. py-editor::
+ :env: rsenv
+
+ # Solution getsta.py
+ import spiceypy
+
+ def getsta(utctim='2004 jun 11 19:32:00'):
+ METAKR = 'getsta.tm'
+ spiceypy.furnsh(METAKR)
+ print(f'Converting UTC Time: {utctim}')
+ et = spiceypy.str2et(utctim)
+ print(f' ET seconds past J2000: {et:16.3f}')
+
+ # Compute the apparent state of Phoebe as seen from CASSINI in the
+ # J2000 frame. Ephemeris readers return km and km/s.
+ state, ltime = spiceypy.spkezr('PHOEBE', et, 'J2000', 'LT+S', 'CASSINI')
+
+ print(' Apparent state of Phoebe as seen from CASSINI in the J2000\n'
+ ' frame (km, km/s):')
+ print(f' X = {state[0]:16.3f}')
+ print(f' Y = {state[1]:16.3f}')
+ print(f' Z = {state[2]:16.3f}')
+ print(f' VX = {state[3]:16.3f}')
+ print(f' VY = {state[4]:16.3f}')
+ print(f' VZ = {state[5]:16.3f}')
+
+ # Compute the apparent position of Earth as seen from CASSINI.
+ # Note: spkpos instead of spkezr since we only need position.
+ pos, ltime = spiceypy.spkpos('EARTH', et, 'J2000', 'LT+S', 'CASSINI')
+
+ print(' Apparent position of Earth as seen from CASSINI in the J2000\n'
+ ' frame (km):')
+ print(f' X = {pos[0]:16.3f}')
+ print(f' Y = {pos[1]:16.3f}')
+ print(f' Z = {pos[2]:16.3f}')
+
+ # ltime is the one-way light time between CASSINI and Earth.
+ print(f' One way light time between CASSINI and the apparent position\n'
+ f' of Earth (seconds): {ltime:16.3f}')
+
+ # Compute the apparent position of the Sun as seen from Phoebe.
+ pos, ltime = spiceypy.spkpos('SUN', et, 'J2000', 'LT+S', 'PHOEBE')
+
+ print(' Apparent position of Sun as seen from Phoebe in the\n'
+ ' J2000 frame (km):')
+ print(f' X = {pos[0]:16.3f}')
+ print(f' Y = {pos[1]:16.3f}')
+ print(f' Z = {pos[2]:16.3f}')
+
+ # For the actual (geometric) distance we use no aberration correction,
+ # then convert km to AU.
+ pos, _ = spiceypy.spkpos('SUN', et, 'J2000', 'NONE', 'PHOEBE')
+ dist = spiceypy.convrt(spiceypy.vnorm(pos), 'KM', 'AU')
+
+ print(f' Actual distance between Sun and Phoebe body centers:\n'
+ f' (AU): {dist:16.3f}')
+
+ spiceypy.unload(METAKR)
+
+ getsta()
Solution Sample Output
@@ -1186,9 +1107,11 @@ Solution Meta-Kernel
The meta-kernel we created for the solution to this exercise is named
'xform.tm'. Its contents follow:
-.. code-block:: text
+.. py-editor::
+ :env: rsenv
- KPL/MK
+ mk = r"""
+ KPL/MK
This is the meta-kernel used in the solution of the "Spacecraft
Orientation and Reference Frames" task in the Remote Sensing
@@ -1219,193 +1142,89 @@ The meta-kernel we created for the solution to this exercise is named
'kernels/ck/04135_04171pc_psiv2.bc',
'kernels/pck/cpck05Mar2004.tpc' )
\begintext
+ """
+ with open('xform.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file xform.tm')
Solution Source Code
A sample solution to the problem follows:
-.. code-block:: python
- :linenos:
-
- #
- # Solution xform.py
- #
- from __future__ import print_function
- from builtins import input
-
- import spiceypy
-
- def xform():
- #
- # Local parameters
- #
- METAKR = 'xform.tm'
-
- #
- # Load the kernels that this program requires. We
- # will need:
- #
- # A leapseconds kernel
- # A spacecraft clock kernel for CASSINI
- # The necessary ephemerides
- # A planetary constants file (PCK)
- # A spacecraft orientation kernel for CASSINI (CK)
- # A frame kernel (TF)
- #
- spiceypy.furnsh( METAKR )
-
- #
- # Prompt the user for the input time string.
- #
- utctim = input( 'Input UTC Time: ' )
-
- print( 'Converting UTC Time: {:s}'.format(utctim) )
-
- #
- #Convert utctim to ET.
- #
- et = spiceypy.str2et( utctim )
-
- print( ' ET seconds past J2000: {:16.3f}'.format(et) )
-
- #
- # Compute the apparent state of Phoebe as seen from
- # CASSINI in the J2000 frame.
- #
- [state, ltime] = spiceypy.spkezr( 'PHOEBE', et, 'J2000',
- 'LT+S', 'CASSINI' )
- #
- # Now obtain the transformation from the inertial
- # J2000 frame to the non-inertial body-fixed IAU_PHOEBE
- # frame. Since we want the apparent position, we
- # need to subtract ltime from et.
- #
- sform = spiceypy.sxform( 'J2000', 'IAU_PHOEBE', et-ltime )
-
- #
- # Now rotate the apparent J2000 state into IAU_PHOEBE
- # with the following matrix multiplication:
- #
- bfixst = spiceypy.mxvg ( sform, state, 6, 6 )
-
- #
- # Display the results.
- #
- print( ' Apparent state of Phoebe as seen '
- 'from CASSINI in the IAU_PHOEBE\n'
- ' body-fixed frame (km, km/s):' )
- print( ' X = {:19.6f}'.format(bfixst[0]) )
- print( ' Y = {:19.6f}'.format(bfixst[1]) )
- print( ' Z = {:19.6f}'.format(bfixst[2]) )
- print( ' VX = {:19.6f}'.format(bfixst[3]) )
- print( ' VY = {:19.6f}'.format(bfixst[4]) )
- print( ' VZ = {:19.6f}'.format(bfixst[5]) )
-
- #
- # It is worth pointing out, all of the above could
- # have been done with a single use of spkezr:
- #
- [state, ltime] = spiceypy.spkezr(
- 'PHOEBE', et, 'IAU_PHOEBE',
- 'LT+S', 'CASSINI' )
- #
- # Display the results.
- #
- print( ' Apparent state of Phoebe as seen '
- 'from CASSINI in the IAU_PHOEBE\n'
- ' body-fixed frame (km, km/s) '
- 'obtained using spkezr directly:' )
- print( ' X = {:19.6f}'.format(state[0]) )
- print( ' Y = {:19.6f}'.format(state[1]) )
- print( ' Z = {:19.6f}'.format(state[2]) )
- print( ' VX = {:19.6f}'.format(state[3]) )
- print( ' VY = {:19.6f}'.format(state[4]) )
- print( ' VZ = {:19.6f}'.format(state[5]) )
-
- #
- # Note that the velocity found by using spkezr
- # to compute the state in the IAU_PHOEBE frame differs
- # at the few mm/second level from that found previously
- # by calling spkezr and then sxform. Computing
- # velocity via a single call to spkezr as we've
- # done immediately above is slightly more accurate because
- # it accounts for the effect of the rate of change of
- # light time on the apparent angular velocity of the
- # target's body-fixed reference frame.
- #
- # Now we are to compute the angular separation between
- # the apparent position of the Earth as seen from the
- # orbiter and the nominal boresight of the high gain
- # antenna. First, compute the apparent position of
- # the Earth as seen from CASSINI in the J2000 frame.
- #
- [pos, ltime] = spiceypy.spkpos( 'EARTH', et, 'J2000',
- 'LT+S', 'CASSINI' )
-
- #
- # Now compute the location of the antenna boresight
- # at this same epoch. From reading the frame kernel
- # we know that the antenna boresight is nominally the
- # +Z axis of the CASSINI_HGA frame defined there.
- #
- bsight = [ 0.0, 0.0, 1.0]
-
- #
- # Now compute the rotation matrix from CASSINI_HGA into
- # J2000.
- #
- pform = spiceypy.pxform( 'CASSINI_HGA', 'J2000', et )
-
- #
- # And multiply the result to obtain the nominal
- # antenna boresight in the J2000 reference frame.
- #
- bsight = spiceypy.mxv( pform, bsight )
-
- #
- # Lastly compute the angular separation.
- #
- sep = spiceypy.convrt( spiceypy.vsep(bsight, pos),
- 'RADIANS', 'DEGREES' )
-
- print( ' Angular separation between the '
- 'apparent position of\n'
- ' Earth and the CASSINI high '
- 'gain antenna boresight (degrees):\n'
- ' {:16.3f}'.format(sep) )
-
- #
- # Or alternatively we can work in the antenna
- # frame directly.
- #
- [pos, ltime] = spiceypy.spkpos(
- 'EARTH', et, 'CASSINI_HGA',
- 'LT+S', 'CASSINI' )
-
- #
- # The antenna boresight is the Z-axis in the
- # CASSINI_HGA frame.
- #
- bsight = [ 0.0, 0.0, 1.0 ]
-
- #
- # Lastly compute the angular separation.
- #
- sep = spiceypy.convrt( spiceypy.vsep(bsight, pos),
- 'RADIANS', 'DEGREES' )
-
- print( ' Angular separation between the '
- 'apparent position of\n'
- ' Earth and the CASSINI high '
- 'gain antenna boresight computed\n'
- ' using vectors in the CASSINI_HGA '
- 'frame (degrees):\n'
- ' {:16.3f}'.format(sep) )
-
- spiceypy.unload( METAKR )
-
- if __name__ == '__main__':
- xform()
+.. py-editor::
+ :env: rsenv
+
+ #
+ # Solution xform.py
+ #
+ import spiceypy
+ def xform(utctim='2004 jun 11 19:32:00'):
+ METAKR = 'xform.tm'
+ spiceypy.furnsh(METAKR)
+ print(f'Converting UTC Time: {utctim}')
+ et = spiceypy.str2et(utctim)
+ print(f' ET seconds past J2000: {et:16.3f}')
+
+ # Compute the apparent state of Phoebe as seen from CASSINI in J2000.
+ state, ltime = spiceypy.spkezr('PHOEBE', et, 'J2000', 'LT+S', 'CASSINI')
+
+ # Obtain the state transformation from J2000 to the non-inertial
+ # body-fixed IAU_PHOEBE frame at the light-time corrected epoch.
+ sform = spiceypy.sxform('J2000', 'IAU_PHOEBE', et - ltime)
+
+ # Rotate the apparent J2000 state into IAU_PHOEBE.
+ bfixst = spiceypy.mxvg(sform, state)
+
+ print(' Apparent state of Phoebe as seen from CASSINI in the IAU_PHOEBE\n'
+ ' body-fixed frame (km, km/s):')
+ print(f' X = {bfixst[0]:19.6f}')
+ print(f' Y = {bfixst[1]:19.6f}')
+ print(f' Z = {bfixst[2]:19.6f}')
+ print(f' VX = {bfixst[3]:19.6f}')
+ print(f' VY = {bfixst[4]:19.6f}')
+ print(f' VZ = {bfixst[5]:19.6f}')
+
+ # All of the above can be done with a single spkezr call directly
+ # into the target frame. This is slightly more accurate for velocity
+ # because it accounts for the rate of change of light time on the
+ # apparent angular velocity of the body-fixed frame.
+ state, ltime = spiceypy.spkezr('PHOEBE', et, 'IAU_PHOEBE', 'LT+S', 'CASSINI')
+
+ print(' Apparent state of Phoebe as seen from CASSINI in the IAU_PHOEBE\n'
+ ' body-fixed frame (km, km/s) obtained using spkezr directly:')
+ print(f' X = {state[0]:19.6f}')
+ print(f' Y = {state[1]:19.6f}')
+ print(f' Z = {state[2]:19.6f}')
+ print(f' VX = {state[3]:19.6f}')
+ print(f' VY = {state[4]:19.6f}')
+ print(f' VZ = {state[5]:19.6f}')
+
+ # Compute the angular separation between the apparent position of Earth
+ # as seen from CASSINI and the nominal HGA boresight (+Z of CASSINI_HGA).
+ pos, _ = spiceypy.spkpos('EARTH', et, 'J2000', 'LT+S', 'CASSINI')
+
+ # Rotate the nominal boresight from CASSINI_HGA into J2000.
+ bsight = spiceypy.mxv(spiceypy.pxform('CASSINI_HGA', 'J2000', et),
+ [0.0, 0.0, 1.0])
+
+ sep = spiceypy.convrt(spiceypy.vsep(bsight, pos), 'RADIANS', 'DEGREES')
+ print(f' Angular separation between the apparent position of\n'
+ f' Earth and the CASSINI high gain antenna boresight (degrees):\n'
+ f' {sep:16.3f}')
+
+ # Alternatively, work directly in the antenna frame.
+ pos, _ = spiceypy.spkpos('EARTH', et, 'CASSINI_HGA', 'LT+S', 'CASSINI')
+
+ # The antenna boresight is the Z-axis in the CASSINI_HGA frame.
+ sep = spiceypy.convrt(spiceypy.vsep([0.0, 0.0, 1.0], pos), 'RADIANS', 'DEGREES')
+ print(' Angular separation between the apparent position of\n'
+ ' Earth and the CASSINI high gain antenna boresight computed\n'
+ ' using vectors in the CASSINI_HGA frame (degrees):\n'
+ f' {sep:16.3f}')
+
+ spiceypy.unload(METAKR)
+
+ #xform()
Solution Sample Output
@@ -1644,145 +1463,81 @@ Solution Meta-Kernel
The meta-kernel we created for the solution to this exercise is named
'subpts.tm'. Its contents follow:
-.. code-block:: text
-
- KPL/MK
-
- This is the meta-kernel used in the solution of the
- "Computing Sub-spacecraft and Sub-solar Points" task
- in the Remote Sensing Hands On Lesson.
-
- The names and contents of the kernels referenced by this
- meta-kernel are as follows:
-
- File name Contents
- -------------------------- -----------------------------
- naif0008.tls Generic LSK
- 981005_PLTEPH-DE405S.bsp Solar System Ephemeris
- 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris
- 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK
- cpck05Mar2004.tpc Cassini Project PCK
- phoebe_64q.bds Phoebe DSK
-
-
- \begindata
- KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
- 'kernels/spk/981005_PLTEPH-DE405S.bsp',
- 'kernels/spk/020514_SE_SAT105.bsp',
- 'kernels/spk/030201AP_SK_SM546_T45.bsp',
- 'kernels/pck/cpck05Mar2004.tpc'
- 'kernels/dsk/phoebe_64q.bds' )
-
- \begintext
+.. py-editor::
+ :env: rsenv
+
+ mk = r"""
+ KPL/MK
+
+ This is the meta-kernel used in the solution of the
+ "Computing Sub-spacecraft and Sub-solar Points" task
+ in the Remote Sensing Hands On Lesson.
+
+ The names and contents of the kernels referenced by this
+ meta-kernel are as follows:
+
+ File name Contents
+ -------------------------- -----------------------------
+ naif0008.tls Generic LSK
+ 981005_PLTEPH-DE405S.bsp Solar System Ephemeris
+ 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris
+ 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK
+ cpck05Mar2004.tpc Cassini Project PCK
+ phoebe_64q.bds Phoebe DSK
+
+
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/spk/981005_PLTEPH-DE405S.bsp',
+ 'kernels/spk/020514_SE_SAT105.bsp',
+ 'kernels/spk/030201AP_SK_SM546_T45.bsp',
+ 'kernels/pck/cpck05Mar2004.tpc',
+ 'kernels/dsk/phoebe_64q.bds' )
+
+ \begintext
+ """
+ with open('subpts.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file subpts.tm')
Solution Source Code
A sample solution to the problem follows:
-.. code-block:: python
- :linenos:
-
- #
- # Solution subpts.py
- #
- from __future__ import print_function
- from builtins import input
-
- #
- # SpiceyPy package:
- #
- import spiceypy
-
- def subpts():
- #
- # Local parameters
- #
- METAKR = 'subpts.tm'
-
- #
- # Load the kernels that this program requires. We
- # will need:
- #
- # A leapseconds kernel
- # The necessary ephemerides
- # A planetary constants file (PCK)
- # A DSK file containing Phoebe shape data
- #
- spiceypy.furnsh( METAKR )
-
- #
- #Prompt the user for the input time string.
- #
- utctim = input( 'Input UTC Time: ' )
-
- print( ' Converting UTC Time: {:s}'.format(utctim) )
-
- #
- #Convert utctim to ET.
- #
- et = spiceypy.str2et( utctim )
-
- print( ' ET seconds past J2000: {:16.3f}'.format(et) )
-
- for i in range(2):
-
- if i == 0:
- #
- # Use the "near point" sub-point definition
- # and an ellipsoidal model.
- #
- method = 'NEAR POINT/Ellipsoid'
-
- else:
- #
- # Use the "nadir" sub-point definition
- # and a DSK model.
- #
- method = 'NADIR/DSK/Unprioritized'
-
- print( '\n Sub-point/target shape model: {:s}\n'.format(
- method ) )
-
- #
- # Compute the apparent sub-observer point of CASSINI
- # on Phoebe.
- #
- [spoint, trgepc, srfvec] = spiceypy.subpnt(
- method, 'PHOEBE', et,
- 'IAU_PHOEBE', 'LT+S', 'CASSINI' )
-
- print( ' Apparent sub-observer point of CASSINI '
- 'on Phoebe in the\n'
- ' IAU_PHOEBE frame (km):' )
- print( ' X = {:16.3f}'.format(spoint[0]) )
- print( ' Y = {:16.3f}'.format(spoint[1]) )
- print( ' Z = {:16.3f}'.format(spoint[2]) )
- print( ' ALT = {:16.3f}'.format(spiceypy.vnorm(srfvec)) )
-
- #
- # Compute the apparent sub-solar point on Phoebe
- # as seen from CASSINI.
- #
- [spoint, trgepc, srfvec] = spiceypy.subslr(
- method, 'PHOEBE', et,
- 'IAU_PHOEBE', 'LT+S', 'CASSINI' )
-
- print( ' Apparent sub-solar point on Phoebe '
- 'as seen from CASSINI in\n'
- ' the IAU_PHOEBE frame (km):' )
- print( ' X = {:16.3f}'.format(spoint[0]) )
- print( ' Y = {:16.3f}'.format(spoint[1]) )
- print( ' Z = {:16.3f}'.format(spoint[2]) )
-
- #
- # End of computation block for "method"
- #
- print( " )
-
- spiceypy.unload( METAKR )
-
- if __name__ == '__main__':
- subpts()
+.. py-editor::
+ :env: rsenv
+
+ # Solution subpts.py
+ import spiceypy
+
+ def subpts(utctim='2004 jun 11 19:32:00'):
+ METAKR = 'subpts.tm'
+ spiceypy.furnsh(METAKR)
+ print(f'Converting UTC Time: {utctim}')
+ et = spiceypy.str2et(utctim)
+ print(f' ET seconds past J2000: {et:16.3f}')
+ # Compute sub-points using both an ellipsoidal and a DSK shape model.
+ for method in ('NEAR POINT/Ellipsoid', 'NADIR/DSK/Unprioritized'):
+ print(f'\n Sub-point/target shape model: {method}\n')
+ # Compute the apparent sub-observer point of CASSINI on Phoebe.
+ spoint, trgepc, srfvec = spiceypy.subpnt(
+ method, 'PHOEBE', et, 'IAU_PHOEBE', 'LT+S', 'CASSINI')
+ print(' Apparent sub-observer point of CASSINI on Phoebe in the\n'
+ ' IAU_PHOEBE frame (km):')
+ print(f' X = {spoint[0]:16.3f}')
+ print(f' Y = {spoint[1]:16.3f}')
+ print(f' Z = {spoint[2]:16.3f}')
+ print(f' ALT = {spiceypy.vnorm(srfvec):16.3f}')
+ # Compute the apparent sub-solar point on Phoebe as seen from CASSINI.
+ spoint, trgepc, srfvec = spiceypy.subslr(method, 'PHOEBE', et, 'IAU_PHOEBE', 'LT+S', 'CASSINI')
+ print(' Apparent sub-solar point on Phoebe as seen from CASSINI in\n'
+ ' the IAU_PHOEBE frame (km):')
+ print(f' X = {spoint[0]:16.3f}')
+ print(f' Y = {spoint[1]:16.3f}')
+ print(f' Z = {spoint[2]:16.3f}')
+ spiceypy.kclear()
+
+ subpts()
Solution Sample Output
@@ -2059,280 +1814,154 @@ Solution Meta-Kernel
The meta-kernel we created for the solution to this exercise is named
'fovint.tm'. Its contents follow:
-.. code-block:: text
-
- KPL/MK
-
- This is the meta-kernel used in the solution of the
- "Intersecting Vectors with a Triaxial Ellipsoid" task
- in the Remote Sensing Hands On Lesson.
-
- The names and contents of the kernels referenced by this
- meta-kernel are as follows:
-
- File name Contents
- -------------------------- -----------------------------
- naif0008.tls Generic LSK
- cas00084.tsc Cassini SCLK
- 981005_PLTEPH-DE405S.bsp Solar System Ephemeris
- 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris
- 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK
- cas_v37.tf Cassini FK
- 04135_04171pc_psiv2.bc Cassini Spacecraft CK
- cpck05Mar2004.tpc Cassini Project PCK
- cas_iss_v09.ti ISS Instrument Kernel
- phoebe_64q.bds Phoebe DSK
-
-
- \begindata
- KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
- 'kernels/sclk/cas00084.tsc',
- 'kernels/spk/981005_PLTEPH-DE405S.bsp',
- 'kernels/spk/020514_SE_SAT105.bsp',
- 'kernels/spk/030201AP_SK_SM546_T45.bsp',
- 'kernels/fk/cas_v37.tf',
- 'kernels/ck/04135_04171pc_psiv2.bc',
- 'kernels/pck/cpck05Mar2004.tpc',
- 'kernels/ik/cas_iss_v09.ti'
- 'kernels/dsk/phoebe_64q.bds' )
- \begintext
+.. py-editor::
+ :env: rsenv
+
+ mk = r"""
+ KPL/MK
+
+ This is the meta-kernel used in the solution of the
+ "Intersecting Vectors with a Triaxial Ellipsoid" task
+ in the Remote Sensing Hands On Lesson.
+
+ The names and contents of the kernels referenced by this
+ meta-kernel are as follows:
+
+ File name Contents
+ -------------------------- -----------------------------
+ naif0008.tls Generic LSK
+ cas00084.tsc Cassini SCLK
+ 981005_PLTEPH-DE405S.bsp Solar System Ephemeris
+ 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris
+ 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK
+ cas_v37.tf Cassini FK
+ 04135_04171pc_psiv2.bc Cassini Spacecraft CK
+ cpck05Mar2004.tpc Cassini Project PCK
+ cas_iss_v09.ti ISS Instrument Kernel
+ phoebe_64q.bds Phoebe DSK
+
+
+ \begindata
+ KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls',
+ 'kernels/sclk/cas00084.tsc',
+ 'kernels/spk/981005_PLTEPH-DE405S.bsp',
+ 'kernels/spk/020514_SE_SAT105.bsp',
+ 'kernels/spk/030201AP_SK_SM546_T45.bsp',
+ 'kernels/fk/cas_v37.tf',
+ 'kernels/ck/04135_04171pc_psiv2.bc',
+ 'kernels/pck/cpck05Mar2004.tpc',
+ 'kernels/ik/cas_iss_v09.ti',
+ 'kernels/dsk/phoebe_64q.bds' )
+ \begintext
+ """
+ with open('fovint.tm', 'w') as dst:
+ dst.write(mk)
+ print('Wrote kernel file fovint.tm')
Solution Source Code
A sample solution to the problem follows:
-.. code-block:: python
- :linenos:
-
- #
- # Solution fovint.py
- #
- from __future__ import print_function
- from builtins import input
-
- #
- # SpiceyPy package:
- #
- import spiceypy
- from spiceypy.utils.support_types import SpiceyError
-
- def fovint():
- #
- # Local parameters
- #
- METAKR = 'fovint.tm'
- ROOM = 4
-
- #
- # Load the kernels that this program requires. We
- # will need:
- #
- # A leapseconds kernel.
- # A SCLK kernel for CASSINI.
- # Any necessary ephemerides.
- # The CASSINI frame kernel.
- # A CASSINI C-kernel.
- # A PCK file with Phoebe constants.
- # The CASSINI ISS I-kernel.
- # A DSK file containing Phoebe shape data.
- #
- spiceypy.furnsh( METAKR )
-
- #
- #Prompt the user for the input time string.
- #
- utctim = input( 'Input UTC Time: ' )
-
- print( 'Converting UTC Time: {:s}'.format(utctim) )
-
- #
- #Convert utctim to ET.
- #
- et = spiceypy.str2et( utctim )
-
- print( ' ET seconds past J2000: {:16.3f}\n'.format(et) )
-
- #
- # Now we need to obtain the FOV configuration of
- # the ISS NAC camera. To do this we will need the
- # ID code for CASSINI_ISS_NAC.
- #
- try:
- nacid = spiceypy.bodn2c( 'CASSINI_ISS_NAC' )
-
- except SpiceyError:
- #
- # Stop the program if the code was not found.
- #
- print( 'Unable to locate the ID code for '
- 'CASSINI_ISS_NAC' )
- raise
-
- #
- # Now retrieve the field of view parameters.
- #
- [ shape, insfrm,
- bsight, n, bounds ] = spiceypy.getfov( nacid, ROOM )
-
- #
- # `bounds' is a numpy array. We'll convert it to a list.
- #
- # Rather than treat BSIGHT as a separate vector,
- # copy it into the last slot of BOUNDS.
- #
- bounds = bounds.tolist()
- bounds.append( bsight )
-
- #
- # Set vector names to be used for output.
- #
- vecnam = [ 'Boundary Corner 1',
- 'Boundary Corner 2',
- 'Boundary Corner 3',
- 'Boundary Corner 4',
- 'Cassini NAC Boresight' ]
-
- #
- # Set values of "method" string that specify use of
- # ellipsoidal and DSK (topographic) shape models.
- #
- # In this case, we can use the same methods for calls to both
- # spiceypy.sincpt and spiceypy.ilumin. Note that some SPICE
- # routines require different "method" inputs from those
- # shown here. See the API documentation of each routine
- # for details.
- #
- method = [ 'Ellipsoid', 'DSK/Unprioritized']
-
- #
- # Get ID code of Phoebe. We'll use this ID code later, when we
- # compute local solar time.
- #
- try:
- phoeid = spiceypy.bodn2c( 'PHOEBE' )
- except:
- #
- # The ID code for PHOEBE is built-in to the library.
- # However, it is good programming practice to get
- # in the habit of handling exceptions that may
- # be thrown when a quantity is not found.
- #
- print( 'Unable to locate the body ID code '
- 'for Phoebe.' )
- raise
-
- #
- # Now perform the same set of calculations for each
- # vector listed in the BOUNDS array. Use both
- # ellipsoidal and detailed (DSK) shape models.
- #
- for i in range(5):
- #
- # Call sincpt to determine coordinates of the
- # intersection of this vector with the surface
- # of Phoebe.
- #
- print( 'Vector: {:s}\n'.format( vecnam[i] ) )
-
- for j in range(2):
-
- print ( ' Target shape model: {:s}\n'.format(
- method[j] ) )
- try:
-
- [point, trgepc, srfvec ] = spiceypy.sincpt(
- method[j], 'PHOEBE', et,
- 'IAU_PHOEBE', 'LT+S', 'CASSINI',
- insfrm, bounds[i] )
-
- #
- # Now, we have discovered a point of intersection.
- # Start by displaying the position vector in the
- # IAU_PHOEBE frame of the intersection.
- #
- print( ' Position vector of surface intercept '
- 'in the IAU_PHOEBE frame (km):' )
- print( ' X = {:16.3f}'.format( point[0] ) )
- print( ' Y = {:16.3f}'.format( point[1] ) )
- print( ' Z = {:16.3f}'.format( point[2] ) )
-
- #
- # Display the planetocentric latitude and longitude
- # of the intercept.
- #
- [radius, lon, lat] = spiceypy.reclat( point )
-
- print( ' Planetocentric coordinates of '
- 'the intercept (degrees):' )
- print( ' LAT = {:16.3f}'.format(
- lat * spiceypy.dpr() ) )
- print( ' LON = {:16.3f}'.format(
- lon * spiceypy.dpr() ) )
- #
- # Compute the illumination angles at this
- # point.
- #
- [ trgepc, srfvec, phase, solar, \
- emissn, visibl, lit ] = \
- spiceypy.illumf(
- method[j], 'PHOEBE', 'SUN', et,
- 'IAU_PHOEBE', 'LT+S', 'CASSINI', point )
-
- print( ' Phase angle (degrees): '
- '{:16.3f}'.format( phase*spiceypy.dpr() ) )
- print( ' Solar incidence angle (degrees): '
- '{:16.3f}'.format( solar*spiceypy.dpr() ) )
- print( ' Emission angle (degrees): '
- '{:16.3f}'.format( emissn*spiceypy.dpr()) )
- print( ' Observer visible: {:s}'.format(
- str(visibl) ) )
- print( ' Sun visible: {:s}'.format(
- str(lit) ) )
-
- if i == 4:
- #
- # Compute local solar time corresponding
- # to the light time corrected TDB epoch
- # at the boresight intercept.
- #
- [hr, mn, sc, time, ampm] = spiceypy.et2lst(
- trgepc,
- phoeid,
- lon,
- 'PLANETOCENTRIC' )
-
- print( '\n Local Solar Time at boresight '
- 'intercept (24 Hour Clock):\n'
- ' {:s}'.format( time ) )
- #
- # End of LST computation block.
- #
-
- except SpiceyError as exc:
- #
- # Display a message if an exception was thrown.
- # For simplicity, we treat this as an indication
- # that the point of intersection was not found,
- # although it could be due to other errors.
- # Otherwise, continue with the calculations.
- #
- print( 'Exception message is: {:s}'.format(
- exc.value ))
- #
- # End of SpiceyError try-catch block.
- #
- print( " )
- #
- # End of target shape model loop.
- #
- #
- # End of vector loop.
- #
-
- spiceypy.unload( METAKR )
-
- if __name__ == '__main__':
- fovint()
+.. py-editor::
+ :env: rsenv
+
+ #
+ # Solution fovint.py
+ #
+ import spiceypy
+
+ def fovint(utctim='2004 jun 11 19:32:00'):
+ METAKR = 'fovint.tm'
+ spiceypy.furnsh(METAKR)
+ print(f'Converting UTC Time: {utctim}')
+ et = spiceypy.str2et(utctim)
+ print(f' ET seconds past J2000: {et:16.3f}\n')
+ # Obtain the NAIF ID and FOV configuration for the ISS NAC camera.
+ try:
+ nacid = spiceypy.bodn2c('CASSINI_ISS_NAC')
+ except spiceypy.SpiceyError:
+ print('Unable to locate the ID code for CASSINI_ISS_NAC')
+ raise
+
+ # getfov returns boundary corner vectors; append the boresight so we
+ # can iterate over all vectors uniformly.
+ shape, insfrm, bsight, n, bounds = spiceypy.getfov(nacid, 4)
+ bounds = bounds.tolist()
+ bounds.append(bsight)
+
+ vec_names = [
+ 'Boundary Corner 1',
+ 'Boundary Corner 2',
+ 'Boundary Corner 3',
+ 'Boundary Corner 4',
+ 'Cassini NAC Boresight',
+ ]
+
+ # Shape model methods for sincpt and illumf — note that some SPICE
+ # routines require different method strings; see each routine's docs.
+ shape_models = ['Ellipsoid', 'DSK/Unprioritized']
+
+ # Obtain the NAIF ID for Phoebe (needed for local solar time).
+ try:
+ phoeid = spiceypy.bodn2c('PHOEBE')
+ except spiceypy.SpiceyError:
+ print('Unable to locate the body ID code for Phoebe.')
+ raise
+
+ # For each FOV vector, intersect with Phoebe using both shape models.
+ for i, (vec_name, vec) in enumerate(zip(vec_names, bounds)):
+ print(f'Vector: {vec_name}\n')
+ is_boresight = (i == len(vec_names) - 1)
+ for shape_model in shape_models:
+ print(f' Target shape model: {shape_model}\n')
+ try:
+ point, trgepc, srfvec = spiceypy.sincpt(
+ shape_model, 'PHOEBE', et,
+ 'IAU_PHOEBE', 'LT+S', 'CASSINI',
+ insfrm, vec)
+
+ # Display the intercept position in the IAU_PHOEBE frame.
+ print(' Position vector of surface intercept '
+ 'in the IAU_PHOEBE frame (km):')
+ print(f' X = {point[0]:16.3f}')
+ print(f' Y = {point[1]:16.3f}')
+ print(f' Z = {point[2]:16.3f}')
+
+ # Display planetocentric coordinates of the intercept.
+ radius, lon, lat = spiceypy.reclat(point)
+ dpr = spiceypy.dpr()
+ print(' Planetocentric coordinates of the intercept (degrees):')
+ print(f' LAT = {lat * dpr:16.3f}')
+ print(f' LON = {lon * dpr:16.3f}')
+
+ # Compute illumination angles at the intercept point.
+ trgepc, srfvec, phase, solar, emissn, visibl, lit = \
+ spiceypy.illumf(
+ shape_model, 'PHOEBE', 'SUN', et,
+ 'IAU_PHOEBE', 'LT+S', 'CASSINI', point)
+
+ print(f' Phase angle (degrees): {phase * dpr:16.3f}')
+ print(f' Solar incidence angle (degrees): {solar * dpr:16.3f}')
+ print(f' Emission angle (degrees): {emissn * dpr:16.3f}')
+ print(f' Observer visible: {visibl}')
+ print(f' Sun visible: {lit}')
+
+ # For the boresight vector, also compute local solar time.
+ if is_boresight:
+ hr, mn, sc, time, ampm = spiceypy.et2lst(
+ trgepc, phoeid, lon, 'PLANETOCENTRIC')
+ print(f'\n Local Solar Time at boresight '
+ f'intercept (24 Hour Clock):\n {time}')
+
+ except spiceypy.SpiceyError as exc:
+ # Treat as no intersection found; continue with next vector.
+ print(f'Exception message is: {exc.value}')
+
+ print()
+
+ spiceypy.unload(METAKR)
+
+ fovint()
Solution Sample Output
From 35b7ea741d398bdaaeb539cedf660450400e910b Mon Sep 17 00:00:00 2001
From: Andrew Annex <2126916+AndrewAnnex@users.noreply.github.com>
Date: Mon, 9 Mar 2026 22:39:53 -0700
Subject: [PATCH 05/10] added scripts folder for docs, and improved plugin to
use it
---
docs/pyscript_editor.py | 18 +-
docs/remote_sensing.rst | 463 +-----------------
docs/scripts/remote_sensing/convtm.py | 32 ++
docs/scripts/remote_sensing/convtm_make_mk.py | 23 +
docs/scripts/remote_sensing/fovint.py | 97 ++++
docs/scripts/remote_sensing/fovint_make_mk.py | 40 ++
docs/scripts/remote_sensing/getsta.py | 66 +++
docs/scripts/remote_sensing/getsta_make_mk.py | 28 ++
docs/scripts/remote_sensing/subpts.py | 31 ++
docs/scripts/remote_sensing/subpts_make_mk.py | 33 ++
docs/scripts/remote_sensing/xform.py | 71 +++
docs/scripts/remote_sensing/xform_make_mk.py | 36 ++
12 files changed, 484 insertions(+), 454 deletions(-)
create mode 100644 docs/scripts/remote_sensing/convtm.py
create mode 100644 docs/scripts/remote_sensing/convtm_make_mk.py
create mode 100644 docs/scripts/remote_sensing/fovint.py
create mode 100644 docs/scripts/remote_sensing/fovint_make_mk.py
create mode 100644 docs/scripts/remote_sensing/getsta.py
create mode 100644 docs/scripts/remote_sensing/getsta_make_mk.py
create mode 100644 docs/scripts/remote_sensing/subpts.py
create mode 100644 docs/scripts/remote_sensing/subpts_make_mk.py
create mode 100644 docs/scripts/remote_sensing/xform.py
create mode 100644 docs/scripts/remote_sensing/xform_make_mk.py
diff --git a/docs/pyscript_editor.py b/docs/pyscript_editor.py
index 21549e7d..dfb79050 100644
--- a/docs/pyscript_editor.py
+++ b/docs/pyscript_editor.py
@@ -51,6 +51,10 @@
declare it explicitly. For non-setup editors, ``config=`` is emitted
only if no setup block has already claimed the env; otherwise it is
omitted entirely. PyScript reads the config once per named environment.
+:src: Path (relative to the docs source directory) to an external ``.py``
+ file whose contents are read at build time and inlined into the
+ editor. The directive body is ignored when ``:src:`` is given.
+ Sphinx will rebuild the page automatically when the file changes.
:target: If given, an empty ```` is appended after the
editor, useful as a display target for PyScript output.
:setup: If present, adds the ``setup`` attribute to the ``