diff --git a/docs/Makefile b/docs/Makefile index 72121e47..a16e0502 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -19,11 +19,12 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext +.PHONY: help clean html serve dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" + @echo " serve to clean, build HTML, and serve locally" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @@ -49,6 +50,9 @@ help: clean: rm -rf $(BUILDDIR)/* +serve: clean html + python -m http.server -d $(BUILDDIR)/html + html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo diff --git a/docs/_static/css/pyscript_editor.css b/docs/_static/css/pyscript_editor.css new file mode 100644 index 00000000..4b7a299a --- /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: border-radius only; margin-top is injected into shadow DOM via JS */
+py-editor::part(run-button) {
+    border-radius: 0 0 4px 0;
+}
diff --git a/docs/basics.rst b/docs/basics.rst
index 7872aa2e..b8e4dcb0 100644
--- a/docs/basics.rst
+++ b/docs/basics.rst
@@ -38,10 +38,12 @@ explanatory error message.
 A simple example program
 ------------------------
 
-The following calls the SPICE function :py:meth:`spiceypy.spiceypy.tkvrsn` which outputs the version
+The following calls the SPICE function :py:func:`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..5762fe59 100644
--- a/docs/binary_pck.rst
+++ b/docs/binary_pck.rst
@@ -1,5 +1,5 @@
 Binary PCK Hands-On Lesson
-===========================
+==========================
 
 November 20, 2017
 
@@ -34,9 +34,9 @@ this lesson:
 
 These tutorials are available from the NAIF ftp server at JPL:
 
-`https://naif.jpl.nasa.gov/naif/tutorials.html `_
+https://naif.jpl.nasa.gov/naif/tutorials.html
 
-Required Readings
+**Required Readings**
 
 .. tip::
    The `Required Readings `_ are also available on the NAIF website at:
@@ -56,7 +56,7 @@ installation tree.
       time.req         Time conversion
 
 The Permuted Index
-^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Permuted Index `_ is also available on the NAIF website at:
@@ -71,24 +71,32 @@ discover which SpiceyPy functions perform functions of interest, as well
 as the names of the source files that contain these functions.
 
 SpiceyPy API Documentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A SpiceyPy function's parameters specification is available using the
 built-in Python help system.
 
 For example, the Python help function
 
-.. code-block:: python
+.. py-editor::
+    :env: bpenv
+    :config: pyscript_binary_pck.json
+    :setup:
+
+    import spiceypy
+
+.. py-editor::
+    :env: bpenv
 
      import spiceypy
 
      help(spiceypy.str2et)
 
-describes of the str2et function's parameters, while the document
+describes the ``str2et`` function's parameters, while the document
 
-`https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html `_
+https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html
 
-describes extensively the str2et functionality.
+describes extensively the ``str2et`` functionality.
 
 Kernels Used
 ------------
@@ -111,7 +119,7 @@ The following kernels are used in examples provided in this lesson:
 These SPICE kernels are included in the lesson package available from
 the NAIF server at JPL:
 
-`http://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/ `_
+http://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/
 
 SpiceyPy Modules Used
 ---------------------
@@ -151,50 +159,48 @@ their corresponding CSPICE versions for detailed interface
 specifications.
 
 Moon rotation (mrotat)
-------------------------------
+----------------------
 
 Task Statement
 ^^^^^^^^^^^^^^
 
 Write a program that performs the following computations:
 
-.. code-block:: text
+#. Convert the time string 2007 JAN 1 00:00:00 UTC to a double
+   precision number representing seconds past J2000 TDB.
 
-       1.   Convert the time string 2007 JAN 1 00:00:00 UTC to a double
-            precision number representing seconds past J2000 TDB.
+   In the following instructions, we'll call the result of this
+   computation ET.
 
-            In the following instructions, we'll call the result of this
-            computation ET.
+#. Compute the apparent position of the Earth as seen from the
+   Moon in the IAU_MOON reference frame at the epoch ET. Use light
+   time and stellar aberration corrections. Use :py:func:`spiceypy.reclat ` to
+   compute the planetocentric longitude and latitude of the Earth
+   position vector; display these coordinates in degrees.
 
-       2.   Compute the apparent position of the Earth as seen from the
-            Moon in the IAU_MOON reference frame at the epoch ET. Use light
-            time and stellar aberration corrections. Use spiceypy.reclat to
-            compute the planetocentric longitude and latitude of the Earth
-            position vector; display these coordinates in degrees.
+#. Repeat the computation of step 2 using the MOON_ME reference
+   frame. Display the results as above.
 
-       3.   Repeat the computation of step 2 using the MOON_ME reference
-            frame. Display the results as above.
+#. Compute the angular separation of the position vectors found in
+   steps 2 and 3. Display the result in degrees.
 
-       4.   Compute the angular separation of the position vectors found in
-            steps 2 and 3. Display the result in degrees.
+#. Repeat the computation of step 2 using the MOON_PA reference
+   frame. Display the results as above.
 
-       5.   Repeat the computation of step 2 using the MOON_PA reference
-            frame. Display the results as above.
+#. Compute the angular separation of the position vectors found in
+   steps 3 and 5 (these vectors are expressed in the MOON_ME and
+   MOON_PA frames). Display the result in degrees.
 
-       6.   Compute the angular separation of the position vectors found in
-            steps 3 and 5 (these vectors are expressed in the MOON_ME and
-            MOON_PA frames). Display the result in degrees.
+#. Compute the apparent sub-Earth point on the Moon at ET,
+   expressed in the MOON_ME reference frame and using light time
+   and stellar aberration corrections. Convert the sub-Earth point
+   to latitudinal coordinates using :py:func:`spiceypy.reclat `. Display the
+   longitude and latitude of the sub-Earth point in degrees.
 
-       7.   Compute the apparent sub-Earth point on the Moon at ET,
-            expressed in the MOON_ME reference frame and using light time
-            and stellar aberration corrections. Convert the sub-Earth point
-            to latitudinal coordinates using spiceypy.reclat. Display the
-            longitude and latitude of the sub-Earth point in degrees.
+#. Repeat step 7, now using the MOON_PA frame.
 
-       8.   Repeat step 7, now using the MOON_PA frame.
-
-       9.   Compute the distance between the two sub-Earth points found
-            above in steps 7 and 8. Display the result in kilometers.
+#. Compute the distance between the two sub-Earth points found
+   above in steps 7 and 8. Display the result in kilometers.
 
 Learning Goals
 ^^^^^^^^^^^^^^
@@ -209,226 +215,46 @@ Approach
 
 The following "tips" may simplify the solution process.
 
-.. code-block:: text
+- Examine the SPICE kernels provided with this lesson. Use BRIEF
+  to find coverage periods of SPK kernels and binary PCKs. Use
+  COMMNT to view the comment areas of binary PCKs. Examine text
+  kernels, in particular text kernel comments, using a text
+  editor or browser.
 
-       --   Examine the SPICE kernels provided with this lesson. Use BRIEF
-            to find coverage periods of SPK kernels and binary PCKs. Use
-            COMMNT to view the comment areas of binary PCKs. Examine text
-            kernels, in particular text kernel comments, using a text
-            editor or browser.
+- Decide which SPICE kernels are necessary. Prepare a meta-kernel
+  listing the kernels and load it into the program.
 
-       --   Decide which SPICE kernels are necessary. Prepare a meta-kernel
-            listing the kernels and load it into the program.
+- Consult the above list titled "SpiceyPy Modules Used" to see
+  which routines are needed.
 
-       --   Consult the above list titled "SpiceyPy Modules Used" to see
-            which routines are needed.
-
-       --   The computational steps listed above should be followed in the
-            order shown.
+- The computational steps listed above should be followed in the
+  order shown.
 
 You may find it useful to consult the permuted index, the headers of
-various source modules, and the tutorials titled "PCK" and" High
+various source modules, and the tutorials titled "PCK" and "High
 Accuracy Orientation and Body-Fixed frames for Moon and Earth."
 
 Solution
 ^^^^^^^^
 
-Solution Meta-Kernel
+**Solution Meta-Kernel**
 
 The meta-kernel we created for the solution to this exercise is named
 'mrotat.tm'. Its contents follow:
 
-.. code-block:: text
-
-      KPL/MK
-
-      Meta-kernel for the "Moon Rotation" task in the Binary PCK
-      Hands On Lesson.
+.. py-editor::
+    :env: bpenv
+    :src: scripts/binary_pck/mrotat_make_mk.py
 
-      The names and contents of the kernels referenced by this
-      meta-kernel are as follows:
-
-      File name                    Contents
-      ---------------------------  ------------------------------------
-      naif0008.tls                 Generic LSK
-      de414_2000_2020.bsp          Solar System Ephemeris
-      moon_060721.tf               Lunar FK
-      pck00008.tpc                 NAIF text PCK
-      moon_pa_de403_1950-2198.bpc  Moon binary PCK
-
-      \begindata
-
-         KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls'
-                             'kernels/spk/de414_2000_2020.bsp'
-                             'kernels/fk/moon_060721.tf'
-                             'kernels/pck/pck00008.tpc'
-                             'kernels/pck/moon_pa_de403_1950-2198.bpc' )
-      \begintext
-
-Solution Source Code
+**Solution Source Code**
 
 A sample solution to the problem follows:
 
-.. code-block:: python
-
-      #
-      # Solution mrotat
-      #
-      from __future__ import print_function
-
-      #
-      # SpiceyPy package:
-      #
-      import spiceypy
-
-
-      def mrotat():
-          #
-          # Local parameters
-          #
-          METAKR = "mrotat.tm"
-
-          #
-          # Load the kernels that this program requires.
-          #
-          spiceypy.furnsh(METAKR)
-
-          #
-          # Convert our UTC string to seconds past J2000 TDB.
-          #
-          timstr = "2007 JAN 1 00:00:00"
-          et = spiceypy.str2et(timstr)
-
-          #
-          # Look up the apparent position of the Earth relative
-          # to the Moon's center in the IAU_MOON frame at ET.
-          #
-          [imoonv, ltime] = spiceypy.spkpos("earth", et, "iau_moon", "lt+s", "moon")
-
-          #
-          # Express the Earth direction in terms of longitude
-          # and latitude in the IAU_MOON frame.
-          #
-          [r, lon, lat] = spiceypy.reclat(imoonv)
-
-          print(
-              "\n"
-              "Moon-Earth direction using low accuracy\n"
-              "PCK and IAU_MOON frame:\n"
-              "Earth lon (deg):        {0:15.6f}\n"
-              "Earth lat (deg):        {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Look up the apparent position of the Earth relative
-          # to the Moon's center in the MOON_ME frame at ET.
-          #
-          [mmoonv, ltime] = spiceypy.spkpos("earth", et, "moon_me", "lt+s", "moon")
-          #
-          # Express the Earth direction in terms of longitude
-          # and latitude in the MOON_ME frame.
-          #
-          [r, lon, lat] = spiceypy.reclat(mmoonv)
-
-          print(
-              "Moon-Earth direction using high accuracy\n"
-              "PCK and MOON_ME frame:\n"
-              "Earth lon (deg):        {0:15.6f}\n"
-              "Earth lat (deg):        {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Find the angular separation of the Earth position
-          # vectors in degrees.
-          #
-          sep = spiceypy.dpr() * spiceypy.vsep(imoonv, mmoonv)
-
-          print("For IAU_MOON vs MOON_ME frames:")
-          print("Moon-Earth vector separation angle (deg):     " "{:15.6f}\n".format(sep))
-          #
-          # Look up the apparent position of the Earth relative
-          # to the Moon's center in the MOON_PA frame at ET.
-          #
-          [pmoonv, ltime] = spiceypy.spkpos("earth", et, "moon_pa", "lt+s", "moon")
-          #
-          # Express the Earth direction in terms of longitude
-          # and latitude in the MOON_PA frame.
-          #
-          [r, lon, lat] = spiceypy.reclat(pmoonv)
-
-          print(
-              "Moon-Earth direction using high accuracy\n"
-              "PCK and MOON_PA frame:\n"
-              "Earth lon (deg):        {0:15.6f}\n"
-              "Earth lat (deg):        {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Find the angular separation of the Earth position
-          # vectors in degrees.
-          #
-          sep = spiceypy.dpr() * spiceypy.vsep(pmoonv, mmoonv)
-
-          print("For MOON_PA vs MOON_ME frames:")
-          print("Moon-Earth vector separation angle (deg):     " "{:15.6f}\n".format(sep))
-          #
-          # Find the apparent sub-Earth point on the Moon at ET
-          # using the MOON_ME frame.
-          #
-          [msub, trgepc, srfvec] = spiceypy.subpnt(
-              "near point: ellipsoid", "moon", et, "moon_me", "lt+s", "earth"
-          )
-          #
-          # Display the sub-point in latitudinal coordinates.
-          #
-          [r, lon, lat] = spiceypy.reclat(msub)
-
-          print(
-              "Sub-Earth point on Moon using high accuracy\n"
-              "PCK and MOON_ME frame:\n"
-              "Sub-Earth lon (deg):   {0:15.6f}\n"
-              "Sub-Earth lat (deg):   {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Find the apparent sub-Earth point on the Moon at
-          # ET using the MOON_PA frame.
-          #
-          [psub, trgepc, srfvec] = spiceypy.subpnt(
-              "near point: ellipsoid", "moon", et, "moon_pa", "lt+s", "earth"
-          )
-          #
-          # Display the sub-point in latitudinal coordinates.
-          #
-          [r, lon, lat] = spiceypy.reclat(psub)
-
-          print(
-              "Sub-Earth point on Moon using high accuracy\n"
-              "PCK and MOON_PA frame:\n"
-              "Sub-Earth lon (deg):   {0:15.6f}\n"
-              "Sub-Earth lat (deg):   {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Find the distance between the sub-Earth points
-          # in km.
-          #
-          dist = spiceypy.vdist(msub, psub)
-
-          print("Distance between sub-Earth points (km): " "{:15.6f}\n".format(dist))
-
-          spiceypy.unload(METAKR)
-
-
-      if __name__ == "__main__":
-          mrotat()
-
-Solution Sample Output
+.. py-editor::
+    :env: bpenv
+    :src: scripts/binary_pck/mrotat.py
+
+**Solution Sample Output**
 
 Execute the program:
 
@@ -468,7 +294,7 @@ Execute the program:
       Distance between sub-Earth points (km):        0.856182
 
 Earth rotation (erotat)
-------------------------------
+-----------------------
 
 .. _task-statement-1:
 
@@ -477,25 +303,23 @@ Task Statement
 
 Write a program that performs the following computations:
 
-.. code-block:: text
-
-       1.   Convert the time string 2007 JAN 1 00:00:00 UTC to a double
-            precision number representing seconds past J2000 TDB.
+#. Convert the time string 2007 JAN 1 00:00:00 UTC to a double
+   precision number representing seconds past J2000 TDB.
 
-            In the following instructions, we'll call the result of this
-            computation ET.
+   In the following instructions, we'll call the result of this
+   computation ET.
 
-       2.   Compute the apparent position of the Moon as seen from the
-            Earth in the IAU_EARTH reference frame at the epoch ET. Use
-            light time and stellar aberration corrections. Display the
-            planetocentric longitude and latitude of the Moon position
-            vector in degrees.
+#. Compute the apparent position of the Moon as seen from the
+   Earth in the IAU_EARTH reference frame at the epoch ET. Use
+   light time and stellar aberration corrections. Display the
+   planetocentric longitude and latitude of the Moon position
+   vector in degrees.
 
-       3.   Repeat the first computation using the ITRF93 reference frame.
-            Display the results as above.
+#. Repeat the first computation using the ITRF93 reference frame.
+   Display the results as above.
 
-       4.   Compute the angular separation of the position vectors found
-            the the previous two steps. Display the result in degrees.
+#. Compute the angular separation of the position vectors found
+   in the previous two steps. Display the result in degrees.
 
 The following computations (steps 5-10) examine the cause of the angular
 offset found above, which is attributable to the rotation between the
@@ -507,30 +331,28 @@ For each of the two epochs ET and ET + 100 days, examine the differences
 between the axes of the ITRF93 and IAU_EARTH frames using the following
 method:
 
-.. code-block:: text
-
-       5.   Convert the epoch of interest to a string in the format style
-            "2007-MAY-16 02:29:00.000 (UTC)." Display this string.
+5.  Convert the epoch of interest to a string in the format style
+    "2007-MAY-16 02:29:00.000 (UTC)." Display this string.
 
-       6.   Look up the 3x3 position transformation matrix that converts
-            vectors from the IAU_EARTH to the ITRF93 frame at the epoch of
-            interest. We'll call the returned matrix RMAT.
+6.  Look up the 3x3 position transformation matrix that converts
+    vectors from the IAU_EARTH to the ITRF93 frame at the epoch of
+    interest. We'll call the returned matrix RMAT.
 
-       7.   Extract the first row of RMAT into a 3-vector, which we'll call
-            ITRFX. This is the X-axis of the ITRF93 frame expressed
-            relative to the IAU_EARTH frame.
+7.  Extract the first row of RMAT into a 3-vector, which we'll call
+    ITRFX. This is the X-axis of the ITRF93 frame expressed
+    relative to the IAU_EARTH frame.
 
-       8.   Extract the third row of RMAT into a 3-vector, which we'll call
-            ITRFZ. This is the Z-axis of the ITRF93 frame expressed
-            relative to the IAU_EARTH frame.
+8.  Extract the third row of RMAT into a 3-vector, which we'll call
+    ITRFZ. This is the Z-axis of the ITRF93 frame expressed
+    relative to the IAU_EARTH frame.
 
-       9.   Compute the angular separation between the vector ITRFX and the
-            X-axis (1, 0, 0) of the IAU_EARTH frame. Display the result in
-            degrees.
+9.  Compute the angular separation between the vector ITRFX and the
+    X-axis (1, 0, 0) of the IAU_EARTH frame. Display the result in
+    degrees.
 
-      10.   Compute the angular separation between the vector ITRFZ and the
-            Z-axis (0, 0, 1) of the IAU_EARTH frame. Display the result in
-            degrees.
+10. Compute the angular separation between the vector ITRFZ and the
+    Z-axis (0, 0, 1) of the IAU_EARTH frame. Display the result in
+    degrees.
 
 This is the end of the computations to be performed for the epochs ET
 and ET + 100 days. The following steps are part of a new computation.
@@ -538,26 +360,24 @@ and ET + 100 days. The following steps are part of a new computation.
 Find the azimuth and elevation of the apparent position of the Moon as
 seen from the DSN station DSS-13 by the following steps:
 
-.. code-block:: text
-
-      11.   Find the apparent position vector of the Moon relative to the
-            DSN station DSS-13 in the topocentric reference frame
-            DSS-13_TOPO at epoch ET. Use light time and stellar aberration
-            corrections.
+11. Find the apparent position vector of the Moon relative to the
+    DSN station DSS-13 in the topocentric reference frame
+    DSS-13_TOPO at epoch ET. Use light time and stellar aberration
+    corrections.
 
-            For this step, you'll need to have loaded a station SPK file
-            providing geocentric station position vectors, as well as a
-            frame kernel specifying topocentric reference frames centered
-            at the respective DSN stations. (Other kernels will be needed
-            as well; you must choose these.)
+    For this step, you'll need to have loaded a station SPK file
+    providing geocentric station position vectors, as well as a
+    frame kernel specifying topocentric reference frames centered
+    at the respective DSN stations. (Other kernels will be needed
+    as well; you must choose these.)
 
-      12.   Convert the position vector to latitudinal coordinates. Use the
-            routine spiceypy.reclat for this computation.
+12. Convert the position vector to latitudinal coordinates. Use the
+    routine :py:func:`spiceypy.reclat ` for this computation.
 
-      13.   Compute the Moon's azimuth and elevation as follows: azimuth is
-            the negative of topocentric longitude and lies within the range
-            0-360 degrees; elevation is equal to the topocentric latitude.
-            Display the results in degrees.
+13. Compute the Moon's azimuth and elevation as follows: azimuth is
+    the negative of topocentric longitude and lies within the range
+    0-360 degrees; elevation is equal to the topocentric latitude.
+    Display the results in degrees.
 
 The next computations demonstrate "high-accuracy" geometric
 computations using the Earth as the target body. These computations are
@@ -566,21 +386,19 @@ features used for geometry computations involving the Earth as a target
 body. For example, the same basic techniques would be used to find the
 sub-solar point on the Earth as seen from an Earth-orbiting spacecraft.
 
-.. code-block:: text
-
-      14.   Compute the apparent sub-solar point on the Earth at ET,
-            expressed relative to the IAU_EARTH reference frame, using
-            light time and stellar aberration corrections and using the Sun
-            as the observer. Convert the sub-solar point to latitudinal
-            coordinates using spiceypy.reclat. Display the longitude and
-            latitude of the sub-solar point in degrees.
+14. Compute the apparent sub-solar point on the Earth at ET,
+    expressed relative to the IAU_EARTH reference frame, using
+    light time and stellar aberration corrections and using the Sun
+    as the observer. Convert the sub-solar point to latitudinal
+    coordinates using :py:func:`spiceypy.reclat `. Display the longitude and
+    latitude of the sub-solar point in degrees.
 
-      15.   Repeat the sub-solar point computation described above, using
-            the ITRF93 Earth body-fixed reference frame. Display the
-            results as above.
+15. Repeat the sub-solar point computation described above, using
+    the ITRF93 Earth body-fixed reference frame. Display the
+    results as above.
 
-      16.   Compute the distance between the two sub-solar points found
-            above. Display the result in kilometers.
+16. Compute the distance between the two sub-solar points found
+    above. Display the result in kilometers.
 
 .. _learning-goals-1:
 
@@ -602,33 +420,31 @@ Approach
 
 The following "tips" may simplify the solution process.
 
-.. code-block:: text
-
-       --   Examine the SPICE kernels provided with this lesson. Use BRIEF
-            to find coverage periods of SPK kernels and binary PCKs. Use
-            COMMNT to view the comment areas of binary PCKs. Examine text
-            kernels, in particular text kernel comments, using a text
-            editor or browser.
+- Examine the SPICE kernels provided with this lesson. Use BRIEF
+  to find coverage periods of SPK kernels and binary PCKs. Use
+  COMMNT to view the comment areas of binary PCKs. Examine text
+  kernels, in particular text kernel comments, using a text
+  editor or browser.
 
-       --   Decide which SPICE kernels are necessary. Prepare a meta-kernel
-            listing the kernels and load it into the program.
+- Decide which SPICE kernels are necessary. Prepare a meta-kernel
+  listing the kernels and load it into the program.
 
-       --   Consult the above list titled "SpiceyPy Modules Used" to see
-            which routines are needed. Note the functions used to provide
-            the values "seconds per day," "degrees per radian," and "2
-            times Pi."
+- Consult the above list titled "SpiceyPy Modules Used" to see
+  which routines are needed. Note the functions used to provide
+  the values "seconds per day," "degrees per radian," and "2
+  times Pi."
 
-       --   Examine the header of the function spiceypy.reclat. Note that
-            this function may be used for coordinate conversions in
-            situations where the input rectangular coordinates refer to any
-            reference frame, not only a body-centered, body-fixed frame
-            whose X-Y plane coincides with the body's equator.
+- Examine the header of the function :py:func:`spiceypy.reclat `. Note that
+  this function may be used for coordinate conversions in
+  situations where the input rectangular coordinates refer to any
+  reference frame, not only a body-centered, body-fixed frame
+  whose X-Y plane coincides with the body's equator.
 
-       --   The computational steps listed above should be followed in the
-            order shown, but steps 5-10 may be omitted.
+- The computational steps listed above should be followed in the
+  order shown, but steps 5-10 may be omitted.
 
 You may find it useful to consult the permuted index, the headers of
-various source modules, and the tutorials titled "PCK" and" High
+various source modules, and the tutorials titled "PCK" and "High
 Accuracy Orientation and Body-Fixed frames for Moon and Earth."
 
 .. _solution-1:
@@ -636,266 +452,24 @@ Accuracy Orientation and Body-Fixed frames for Moon and Earth."
 Solution
 ^^^^^^^^
 
-Solution Meta-Kernel
+**Solution Meta-Kernel**
 
 The meta-kernel we created for the solution to this exercise is named
 'erotat.tm'. Its contents follow:
 
-.. code-block:: text
-
-      KPL/MK
+.. py-editor::
+    :env: bpenv
+    :src: scripts/binary_pck/erotat_make_mk.py
 
-      Meta-kernel for the "Earth Rotation" task
-      in the Binary PCK 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
-      de414_2000_2020.bsp             Solar System Ephemeris
-      earthstns_itrf93_050714.bsp     DSN station Ephemeris
-      earth_topo_050714.tf            Earth topocentric FK
-      pck00008.tpc                    NAIF text PCK
-      earth_000101_070725_070503.bpc  Earth binary PCK
-
-
-      \begindata
-
-      KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls'
-                          'kernels/spk/de414_2000_2020.bsp'
-                          'kernels/spk/earthstns_itrf93_050714.bsp'
-                          'kernels/fk/earth_topo_050714.tf'
-                          'kernels/pck/pck00008.tpc'
-                          'kernels/pck/earth_000101_070725_070503.bpc' )
-
-      \begintext
-
-Solution Source Code
+**Solution Source Code**
 
 A sample solution to the problem follows:
 
-.. code-block:: python
-
-      #
-      # Solution mrotat
-      #
-      from __future__ import print_function
-
-      #
-      # SpiceyPy package:
-      #
-      import spiceypy
-
-
-      def erotat():
-          #
-          # Local parameters
-          #
-          METAKR = "erotat.tm"
-
-          x = [1.0, 0.0, 0.0]
-          z = [0.0, 0.0, 1.0]
-
-          #
-          # Load the kernels that this program requires.
-          #
-          spiceypy.furnsh(METAKR)
-
-          #
-          # Convert our UTC string to seconds past J2000 TDB.
-          #
-          timstr = "2007 JAN 1 00:00:00"
-          et = spiceypy.str2et(timstr)
-
-          #
-          # Look up the apparent position of the Moon relative
-          # to the Earth's center in the IAU_EARTH frame at ET.
-          #
-          [lmoonv, ltime] = spiceypy.spkpos("moon", et, "iau_earth", "lt+s", "earth")
-          #
-          # Express the Moon direction in terms of longitude
-          # and latitude in the IAU_EARTH frame.
-          #
-          [r, lon, lat] = spiceypy.reclat(lmoonv)
-
-          print(
-              "Earth-Moon direction using low accuracy\n"
-              "PCK and IAU_EARTH frame:\n"
-              "Moon lon (deg):        {0:15.6f}\n"
-              "Moon lat (deg):        {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Look up the apparent position of the Moon relative
-          # to the Earth's center in the ITRF93 frame at ET.
-          #
-          [hmoonv, ltime] = spiceypy.spkpos("moon", et, "ITRF93", "lt+s", "earth")
-          #
-          # Express the Moon direction in terms of longitude
-          # and latitude in the ITRF93 frame.
-          #
-          [r, lon, lat] = spiceypy.reclat(hmoonv)
-
-          print(
-              "Earth-Moon direction using high accuracy\n"
-              "PCK and ITRF93 frame:\n"
-              "Moon lon (deg):        {0:15.6f}\n"
-              "Moon lat (deg):        {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-          #
-          # Find the angular separation of the Moon position
-          # vectors in degrees.
-          #
-          sep = spiceypy.dpr() * spiceypy.vsep(lmoonv, hmoonv)
-
-          print("Earth-Moon vector separation angle (deg):     " "{:15.6f}\n".format(sep))
-
-          #
-          # Next, express the +Z and +X axes of the ITRF93 frame in
-          # the IAU_EARTH frame. We'll do this for two times: et
-          # and et + 100 days.
-          #
-          for i in range(2):
-              #
-              # Set the time, expressing the time delta in
-              # seconds.
-              #
-              t = et + i * spiceypy.spd() * 100
-
-              #
-              # Convert the TDB time T to a string for output.
-              #
-              outstr = spiceypy.timout(t, "YYYY-MON-DD HR:MN:SC.### (UTC)")
-
-              print("Epoch: {:s}".format(outstr))
-
-              #
-              # Find the rotation matrix for conversion of
-              # position vectors from the IAU_EARTH to the
-              # ITRF93 frame.
-              #
-              rmat = spiceypy.pxform("iau_earth", "itrf93", t)
-              itrfx = rmat[0]
-              itrfz = rmat[2]
-
-              #
-              # Display the angular offsets of the ITRF93
-              # +X and +Z axes from their IAU_EARTH counterparts.
-              #
-              sep = spiceypy.vsep(itrfx, x)
-
-              print(
-                  "ITRF93 - IAU_EARTH +X axis separation "
-                  "angle (deg): {:13.6f}".format(sep * spiceypy.dpr())
-              )
-
-              sep = spiceypy.vsep(itrfz, z)
-
-              print(
-                  "ITRF93 - IAU_EARTH +Z axis separation "
-                  "angle (deg): {:13.6f}\n".format(sep * spiceypy.dpr())
-              )
-
-          #
-          # Find the azimuth and elevation of apparent
-          # position of the Moon in the local topocentric
-          # reference frame at the DSN station DSS-13.
-          # First look up the Moon's position relative to the
-          # station in that frame.
-          #
-          [topov, ltime] = spiceypy.spkpos("moon", et, "DSS-13_TOPO", "lt+s", "DSS-13")
-
-          #
-          # Express the station-moon direction in terms of longitude
-          # and latitude in the DSS-13_TOPO frame.
-          #
-          [r, lon, lat] = spiceypy.reclat(topov)
-
-          #
-          # Convert to azimuth-elevation.
-          #
-          az = -lon
-
-          if az < 0.0:
-              az += spiceypy.twopi()
-
-          el = lat
-
-          print(
-              "DSS-13-Moon az/el using high accuracy "
-              "PCK and DSS-13_TOPO frame:\n"
-              "Moon Az (deg):        {0:15.6f}\n"
-              "Moon El (deg):        {1:15.6f}\n".format(
-                  az * spiceypy.dpr(), el * spiceypy.dpr()
-              )
-          )
-
-          #
-          # Find the sub-solar point on the Earth at ET using the
-          # Earth body-fixed frame IAU_EARTH. Treat the Sun as
-          # the observer.
-          #
-          [lsub, trgepc, srfvec] = spiceypy.subslr(
-              "near point: ellipsoid", "earth", et, "IAU_EARTH", "lt+s", "sun"
-          )
-
-          #
-          # Display the sub-point in latitudinal coordinates.
-          #
-          [r, lon, lat] = spiceypy.reclat(lsub)
-
-          print(
-              "Sub-Solar point on Earth using low accuracy\n"
-              "PCK and IAU_EARTH frame:\n"
-              "Sub-Solar lon (deg):   {0:15.6f}\n"
-              "Sub-Solar lat (deg):   {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-
-          #
-          # Find the sub-solar point on the Earth at ET using the
-          # Earth body-fixed frame ITRF93. Treat the Sun as
-          # the observer.
-          #
-          [hsub, trgepc, srfvec] = spiceypy.subslr(
-              "near point: ellipsoid", "earth", et, "ITRF93", "lt+s", "sun"
-          )
-
-          #
-          # Display the sub-point in latitudinal coordinates.
-          #
-          [r, lon, lat] = spiceypy.reclat(hsub)
-
-          print(
-              "Sub-Solar point on Earth using "
-              "high accuracy \nPCK and ITRF93 frame:\n"
-              "Sub-Solar lon (deg):   {0:15.6f}\n"
-              "Sub-Solar lat (deg):   {1:15.6f}\n".format(
-                  lon * spiceypy.dpr(), lat * spiceypy.dpr()
-              )
-          )
-
-          #
-          # Find the distance between the sub-solar point
-          # vectors in km.
-          #
-          dist = spiceypy.vdist(lsub, hsub)
-
-          print("Distance between sub-solar points (km): " "{:15.6f}".format(dist))
-
-          spiceypy.unload(METAKR)
-
-
-      if __name__ == "__main__":
-          erotat()
-
-Solution Sample Output
+.. py-editor::
+    :env: bpenv
+    :src: scripts/binary_pck/erotat.py
+
+**Solution Sample Output**
 
 Execute the program:
 
diff --git a/docs/conf.py b/docs/conf.py
index b7e77bc0..9d49c7fa 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"
 
@@ -200,6 +218,12 @@
 # directly to the root of the documentation.
 html_extra_path = [
     "pyscript.json",
+    "pyscript_binary_pck.json",
+    "pyscript_min.json",
+    "pyscript_event_finding.json",
+    "pyscript_insitu_sensing.json",
+    "pyscript_remote_sensing.json",
+    "pyscript_other_stuff.json",
     "mini-coi.js",
 ]
 
diff --git a/docs/event_finding.rst b/docs/event_finding.rst
index aadb3bdc..57f0d4b7 100644
--- a/docs/event_finding.rst
+++ b/docs/event_finding.rst
@@ -47,14 +47,10 @@ this lesson:
       GF                The SPICE Geometry Finder (GF) subsystem
 
 These tutorials are available from the NAIF ftp server at JPL:
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-.. code-block:: text
-
-      https://naif.jpl.nasa.gov/naif/tutorials.html
+https://naif.jpl.nasa.gov/naif/tutorials.html
 
 Required Readings
-^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Required Readings `_ are also available on the NAIF website at:
@@ -79,7 +75,7 @@ installation tree.
       windows.req      The SPICE window data type
 
 The Permuted Index
-^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Permuted Index `_ is also available on the NAIF website at:
@@ -94,24 +90,31 @@ discover which SpiceyPy functions perform functions of interest, as well
 as the names of the source files that contain these functions.
 
 SpiceyPy API Documentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A SpiceyPy function's parameters specification is available using the
 built-in Python help system.
 
 For example, the Python help function
 
-.. code-block:: python
+.. py-editor::
+    :env: efenv
+    :config: pyscript_event_finding.json
+    :setup:
 
-      >>> import spiceypy
-      >>> help(spiceypy.str2et)
+    import spiceypy
 
-describes of the str2et function's parameters, while the document
+.. py-editor::
+    :env: efenv
+    :config: pyscript_event_finding.json
 
-.. code-block:: text
+    import spiceypy
 
-      https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html
+    help(spiceypy.str2et)
 
+
+describes the str2et function's parameters, while the
+`str2et documentation `_
 describes extensively the str2et functionality.
 
 Kernels Used
@@ -138,10 +141,7 @@ The following kernels are used in examples provided in this lesson:
 
 These SPICE kernels are included in the lesson package available from
 the NAIF server at JPL:
-
-.. code-block:: text
-
-      https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/
+https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/
 
 SpiceyPy Modules Used
 ---------------------
@@ -180,19 +180,13 @@ their corresponding CSPICE versions for detailed interface
 specifications.
 
 Find View Periods
-------------------------------
+-----------------
 
 Task Statement
 ^^^^^^^^^^^^^^
 
 Write a program that finds the set of time intervals, within the time
-range
-
-.. code-block:: text
-
-      2004 MAY 2 TDB
-      2004 MAY 6 TDB
-
+range ``2004 MAY 2 TDB`` to ``2004 MAY 6 TDB``,
 when the Mars Express Orbiter (MEX) is visible from the DSN station
 DSS-14. These time intervals are frequently called "view periods."
 
@@ -218,329 +212,84 @@ parsing and output formatting routines.
 Approach
 ^^^^^^^^
 
-Solution steps
+**Solution steps**
 
 A possible solution could consist of the following steps:
 
-Preparation:
-
-.. code-block:: text
+**Preparation:**
 
-       1.   Decide what SPICE kernels are necessary. Use the SPICE summary
-            tool BRIEF to examine the coverage of the binary kernels and
-            verify the availability of required data.
+#. Decide what SPICE kernels are necessary. Use the SPICE summary
+   tool BRIEF to examine the coverage of the binary kernels and
+   verify the availability of required data.
 
-       2.   Create a meta-kernel listing the SPICE kernels to be loaded.
-            (Hint: consult a programming example tutorial, or the
-            Introduction to Kernels tutorial, for a reminder of how to do
-            this.)
+#. Create a meta-kernel listing the SPICE kernels to be loaded.
+   (Hint: consult a programming example tutorial, or the
+   Introduction to Kernels tutorial, for a reminder of how to do
+   this.)
 
-            Name the meta-kernel 'viewpr.tm'.
+   Name the meta-kernel 'viewpr.tm'.
 
 Next, write a program that performs the following steps:
 
-.. code-block:: text
-
-       1.   Use spiceypy.furnsh to load the meta-kernel.
+#. Use :py:func:`spiceypy.furnsh ` to load the meta-kernel.
 
-       2.   Create confinement and output SpiceyPy windows using
-            stypes.SPICEDOUBLE_CELL.
+#. Create confinement and output SpiceyPy windows using
+   :py:func:`spiceypy.cell_double `.
 
-       3.   Insert the given time bounds into the confinement window using
-            spiceypy.wninsd.
+#. Insert the given time bounds into the confinement window using
+   :py:func:`spiceypy.wninsd `.
 
-       4.   Select a step size for searching for visibility state
-            transitions: in this case, each target rise or set event is a
-            state transition.
+#. Select a step size for searching for visibility state
+   transitions: in this case, each target rise or set event is a
+   state transition.
 
-            The step size must be large enough so the search proceeds with
-            reasonable speed, but small enough so that no visibility
-            transition events---that is, target rise or set events---are
-            missed.
+   The step size must be large enough so the search proceeds with
+   reasonable speed, but small enough so that no visibility
+   transition events---that is, target rise or set events---are
+   missed.
 
-       5.   Use the GF routine spiceypy.gfposc to find the window of times,
-            within the confinement window, during which the MEX spacecraft
-            is above the elevation limit as seen from DSN station DSS-14,
-            in the reference frame DSS-14_TOPO.
+#. Use the GF routine :py:func:`spiceypy.gfposc ` to find the window of times,
+   within the confinement window, during which the MEX spacecraft
+   is above the elevation limit as seen from DSN station DSS-14,
+   in the reference frame DSS-14_TOPO.
 
-            Use light time and stellar aberration corrections for the
-            apparent position of the spacecraft as seen from the station.
+   Use light time and stellar aberration corrections for the
+   apparent position of the spacecraft as seen from the station.
 
-       6.   Fetch and display the contents of the result window. Use
-            spiceypy.wnfetd to extract from the result window the start and
-            stop times of each time interval. Display each of the intervals
-            in the result window as a pair of start and stop times. Express
-            each time as a TDB calendar date using the routine
-            spiceypy.timout.
+#. Fetch and display the contents of the result window. Use
+   :py:func:`spiceypy.wnfetd ` to extract from the result window the start and
+   stop times of each time interval. Display each of the intervals
+   in the result window as a pair of start and stop times. Express
+   each time as a TDB calendar date using the routine
+   :py:func:`spiceypy.timout `.
 
 You may find it useful to consult the references listed above. In
-particular, the header of the SPICE GF function spiceypy.gfposc contains
+particular, the header of the SPICE GF function :py:func:`spiceypy.gfposc ` contains
 pertinent documentation.
 
 Solution
 ^^^^^^^^
 
-Solution Meta-Kernel
+**Solution Meta-Kernel**
 
 The meta-kernel we created for the solution to this exercise is named
 'viewpr.tm'. Its contents follow:
 
-.. code-block:: text
-
-      KPL/MK
-
-         Example meta-kernel for geometric event finding hands-on
-         coding lesson.
-
-            Version 2.0.0 13-JUL-2017 (JDR)
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
-
-         File Name                       Description
-         ------------------------------  ------------------------------
-         de405xs.bsp                     Planetary ephemeris SPK,
-                                         subsetted to cover only
-                                         time range of interest.
-         earthstns_itrf93_050714.bsp     DSN station SPK.
-         earth_topo_050714.tf            DSN station frame definitions.
-         earth_000101_060525_060303.bpc  Binary PCK for Earth.
-         naif0008.tls                    Generic LSK.
-         ORMM__040501000000_00076XS.BSP  MEX Orbiter trajectory SPK,
-                                         subsetted to cover only
-                                         time range of interest.
-         pck00008.tpc                    Generic PCK.
+.. py-editor::
+    :env: efenv
+    :src: scripts/event_finding/viewpr_make_mk.py
 
 
-      \begindata
+**Solution Code**
 
-         KERNELS_TO_LOAD = (
-
-                 'kernels/spk/de405xs.bsp'
-                 'kernels/spk/earthstns_itrf93_050714.bsp'
-                 'kernels/fk/earth_topo_050714.tf'
-                 'kernels/pck/earth_000101_060525_060303.bpc'
-                 'kernels/lsk/naif0008.tls'
-                 'kernels/spk/ORMM__040501000000_00076XS.BSP'
-                 'kernels/pck/pck00008.tpc'
-                           )
-
-      \begintext
+The example program below shows one possible solution.
 
-Solution Code
+.. py-editor::
+    :env: efenv
+    :src: scripts/event_finding/viewpr.py
 
-The example program below shows one possible solution.
 
-.. code-block:: python
-
-      #
-      # Solution viewpr
-      #
-      from __future__ import print_function
-      import spiceypy.utils.support_types as stypes
-      import spiceypy
-
-      def viewpr():
-          #
-          # Local Parameters
-          #
-          METAKR = 'viewpr.tm'
-          TDBFMT = 'YYYY MON DD HR:MN:SC.### (TDB) ::TDB'
-          MAXIVL = 1000
-          MAXWIN = 2 * MAXIVL
-
-          #
-          # Load the meta-kernel.
-          #
-          spiceypy.furnsh( METAKR )
-
-          #
-          # Assign the inputs for our search.
-          #
-          # Since we're interested in the apparent location of the
-          # target, we use light time and stellar aberration
-          # corrections. We use the "converged Newtonian" form
-          # of the light time correction because this choice may
-          # increase the accuracy of the occultation times we'll
-          # compute using gfoclt.
-          #
-          srfpt  = 'DSS-14'
-          obsfrm = 'DSS-14_TOPO'
-          target = 'MEX'
-          abcorr = 'CN+S'
-          start  = '2004 MAY 2 TDB'
-          stop   = '2004 MAY 6 TDB'
-          elvlim =  6.0
-
-          #
-          # The elevation limit above has units of degrees; we convert
-          # this value to radians for computation using SPICE routines.
-          # We'll store the equivalent value in radians in revlim.
-          #
-          revlim = spiceypy.rpd() * elvlim
-
-          #
-          # Since SPICE doesn't directly support the AZ/EL coordinate
-          # system, we use the equivalent constraint
-          #
-          #    latitude > revlim
-          #
-          # in the latitudinal coordinate system, where the reference
-          # frame is topocentric and is centered at the viewing location.
-          #
-          crdsys = 'LATITUDINAL'
-          coord  = 'LATITUDE'
-          relate = '>'
-
-          #
-          # The adjustment value only applies to absolute extrema
-          # searches; simply give it an initial value of zero
-          # for this inequality search.
-          #
-          adjust = 0.0
-
-          #
-          # stepsz is the step size, measured in seconds, used to search
-          # for times bracketing a state transition. Since we don't expect
-          # any events of interest to be shorter than five minutes, and
-          # since the separation between events is well over 5 minutes,
-          # we'll use this value as our step size. Units are seconds.
-          #
-          stepsz = 300.0
-
-          #
-          # Display a banner for the output report:
-          #
-          print( '\n{:s}\n'.format(
-                 'Inputs for target visibility search:' )  )
-
-          print( '   Target                       = '
-                 '{:s}'.format( target )  )
-          print( '   Observation surface location = '
-                 '{:s}'.format( srfpt  )  )
-          print( '   Observer\'s reference frame   = '
-                 '{:s}'.format( obsfrm )  )
-          print( '   Elevation limit (degrees)    = '
-                 '{:f}'.format( elvlim )  )
-          print( '   Aberration correction        = '
-                 '{:s}'.format( abcorr )  )
-          print( '   Step size (seconds)          = '
-                 '{:f}'.format( stepsz )  )
-
-          #
-          # Convert the start and stop times to ET.
-          #
-          etbeg = spiceypy.str2et( start )
-          etend = spiceypy.str2et( stop  )
-
-          #
-          # Display the search interval start and stop times
-          # using the format shown below.
-          #
-          #    2004 MAY 06 20:15:00.000 (TDB)
-          #
-          timstr = spiceypy.timout( etbeg, TDBFMT )
-          print( '   Start time                   = '
-                 '{:s}'.format(timstr) )
-
-          timstr = spiceypy.timout( etend, TDBFMT )
-          print( '   Stop time                    = '
-                 '{:s}'.format(timstr) )
-
-          print( ' ' )
-
-          #
-          # Initialize the "confinement" window with the interval
-          # over which we'll conduct the search.
-          #
-          cnfine = stypes.SPICEDOUBLE_CELL(2)
-          spiceypy.wninsd( etbeg, etend, cnfine )
-
-          #
-          # In the call below, the maximum number of window
-          # intervals gfposc can store internally is set to MAXIVL.
-          # We set the cell size to MAXWIN to achieve this.
-          #
-          riswin = stypes.SPICEDOUBLE_CELL( MAXWIN )
-
-          #
-          # Now search for the time period, within our confinement
-          # window, during which the apparent target has elevation
-          # at least equal to the elevation limit.
-          #
-          spiceypy.gfposc( target, obsfrm, abcorr, srfpt,
-                           crdsys, coord,  relate, revlim,
-                           adjust, stepsz, MAXIVL, cnfine, riswin )
-
-          #
-          # The function wncard returns the number of intervals
-          # in a SPICE window.
-          #
-          winsiz = spiceypy.wncard( riswin )
-
-          if winsiz == 0:
-
-              print( 'No events were found.' )
-
-          else:
-
-              #
-              # Display the visibility time periods.
-              #
-              print( 'Visibility times of {0:s} '
-                     'as seen from {1:s}:\n'.format(
-                      target, srfpt )                )
-
-              for  i  in  range(winsiz):
-                  #
-                  # Fetch the start and stop times of
-                  # the ith interval from the search result
-                  # window riswin.
-                  #
-                  [intbeg, intend] = spiceypy.wnfetd( riswin, i )
-
-                  #
-                  # Convert the rise time to a TDB calendar string.
-                  #
-                  timstr = spiceypy.timout( intbeg, TDBFMT )
-
-                  #
-                  # Write the string to standard output.
-                  #
-                  if  i  ==  0:
-
-                      print( 'Visibility or window start time:'
-                             '  {:s}'.format( timstr )          )
-                  else:
-
-                      print( 'Visibility start time:          '
-                             '  {:s}'.format( timstr )          )
-
-                  #
-                  # Convert the set time to a TDB calendar string.
-                  #
-                  timstr = spiceypy.timout( intend, TDBFMT )
-
-                  #
-                  # Write the string to standard output.
-                  #
-                  if  i  ==  (winsiz-1):
-
-                      print( 'Visibility or window stop time: '
-                             '  {:s}'.format( timstr )          )
-                  else:
-
-                      print( 'Visibility stop time:           '
-                             '  {:s}'.format( timstr )          )
-
-                  print( ' ' )
-
-          spiceypy.unload( METAKR )
-
-      if __name__ == '__main__':
-          viewpr()
-
-Solution Sample Output
+**Solution Sample Output**
 
 Numerical results shown for this example may differ across platforms
 since the results depend on the SPICE kernels used as input and on the
@@ -579,7 +328,7 @@ Execute the program. The output is:
       Visibility or window stop time:   2004 MAY 06 00:00:00.000 (TDB)
 
 Find Times when Target is Visible
-----------------------------------
+---------------------------------
 
 .. _task-statement-ef-1:
 
@@ -589,12 +338,10 @@ Task Statement
 Extend the program of the previous chapter to find times when the MEX
 orbiter is:
 
-.. code-block:: text
-
-       --   Above the elevation limit in the DSS-14_TOPO topocentric
-            reference frame.
+- Above the elevation limit in the DSS-14_TOPO topocentric
+  reference frame.
 
-       --   and is not occulted by Mars
+- And not occulted by Mars.
 
 Finding time intervals that satisfy the second condition requires a
 search for occultations of the spacecraft by Mars. Perform this search
@@ -617,7 +364,7 @@ format as in the previous program.
 Learning Goals
 ^^^^^^^^^^^^^^
 
-Familiarity with the GF occultation finding routine spiceypy.gfoclt.
+Familiarity with the GF occultation finding routine :py:func:`spiceypy.gfoclt `.
 Experience with Digital Shape Kernel (DSK) shape models. Further
 experience with the SpiceyPy window functions.
 
@@ -626,53 +373,51 @@ experience with the SpiceyPy window functions.
 Approach
 ^^^^^^^^
 
-Solution steps
+**Solution steps**
 
 A possible solution would consist of the following steps:
 
-.. code-block:: text
-
-       1.   Use the meta-kernel from the previous chapter as the starting
-            point. Add more kernels to it as needed.
+#. Use the meta-kernel from the previous chapter as the starting
+   point. Add more kernels to it as needed.
 
-            Name the meta-kernel 'visibl.tm'.
+   Name the meta-kernel 'visibl.tm'.
 
-       2.   Include the code from the program of the previous chapter in a
-            new source file; modify this code to create the new program.
+#. Include the code from the program of the previous chapter in a
+   new source file; modify this code to create the new program.
 
-       3.   Your program will need additional windows to capture the
-            results of occultation searches performed using both
-            ellipsoidal and DSK shape models. Additional windows will be
-            needed to compute the set differences of the elevation search
-            ("view period") window and each of the occultation search
-            windows. Further details are provided below.
+#. Your program will need additional windows to capture the
+   results of occultation searches performed using both
+   ellipsoidal and DSK shape models. Additional windows will be
+   needed to compute the set differences of the elevation search
+   ("view period") window and each of the occultation search
+   windows. Further details are provided below.
 
-            Create additional output SpiceyPy windows using
-            stypes.SPICEDOUBLE_CELL.
+   Create additional output SpiceyPy windows using
+   :py:func:`spiceypy.cell_double `.
 
-       4.   The remaining steps can be performed twice: once using an
-            ellipsoidal shape model for Mars, and once using a DSK Mars
-            shape model. Alternatively, two copies of the entire solution
-            program can be created: one for each shape model.
+#. The remaining steps can be performed twice: once using an
+   ellipsoidal shape model for Mars, and once using a DSK Mars
+   shape model. Alternatively, two copies of the entire solution
+   program can be created: one for each shape model.
 
-       5.   Search for occultations of the MEX orbiter as seen from DSS-14
-            using spiceypy.gfoclt. Use as the confinement window for this
-            search the result window from the elevation search performed by
-            spiceypy.gfposc.
+#. Search for occultations of the MEX orbiter as seen from DSS-14
+   using :py:func:`spiceypy.gfoclt `. Use as the confinement window for this
+   search the result window from the elevation search performed by
+   :py:func:`spiceypy.gfposc `.
 
-            Since occultations occur when the apparent MEX spacecraft
-            position is behind the apparent figure of Mars, light time
-            correction must be performed for the occultation search. To
-            improve accuracy of the occultation state determination, use
-            "converged Newtonian" light time correction.
+   Since occultations occur when the apparent MEX spacecraft
+   position is behind the apparent figure of Mars, light time
+   correction must be performed for the occultation search. To
+   improve accuracy of the occultation state determination, use
+   "converged Newtonian" light time correction.
 
-       6.   Use the SpiceyPy window subtraction routine spiceypy.wndifd to
-            subtract the window of times when the spacecraft is occulted
-            from the window of times when the spacecraft is above the
-            elevation limit. The difference window is the final result.
+#. Use the SpiceyPy window subtraction routine :py:func:`spiceypy.wndifd ` to
+   subtract the window of times when the spacecraft is occulted
+   from the window of times when the spacecraft is above the
+   elevation limit. The difference window is the final result.
 
-       7.   Modify the code to display the contents of the difference
-            window.
+#. Modify the code to display the contents of the difference
+   window.
 
 This completes the assignment.
 
@@ -681,339 +426,24 @@ This completes the assignment.
 Solution
 ^^^^^^^^
 
-Solution Meta-Kernel
+**Solution Meta-Kernel**
 
 The meta-kernel we created for the solution to this exercise is named
 'visibl.tm'. Its contents follow:
 
-.. code-block:: text
+.. py-editor::
+    :env: efenv
+    :src: scripts/event_finding/visibl_make_mk.py
+
+
+**Solution Code**
+
+.. py-editor::
+    :env: efenv
+    :src: scripts/event_finding/visibl.py
 
-      KPL/MK
-
-         Example meta-kernel for geometric event finding hands-on
-         coding lesson.
-
-            Version 3.0.0 26-OCT-2017 (BVS)
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
-
-         File Name                       Description
-         ------------------------------  ------------------------------
-         de405xs.bsp                     Planetary ephemeris SPK,
-                                         subsetted to cover only
-                                         time range of interest.
-         earthstns_itrf93_050714.bsp     DSN station SPK.
-         earth_topo_050714.tf            DSN station frame definitions.
-         earth_000101_060525_060303.bpc  Binary PCK for Earth.
-         naif0008.tls                    Generic LSK.
-         ORMM__040501000000_00076XS.BSP  MEX Orbiter trajectory SPK,
-                                         subsetted to cover only
-                                         time range of interest.
-         pck00008.tpc                    Generic PCK.
-         mars_lowres.bds                 Low-resolution Mars DSK.
-
-
-      \begindata
-
-         KERNELS_TO_LOAD = (
-
-                 'kernels/spk/de405xs.bsp'
-                 'kernels/spk/earthstns_itrf93_050714.bsp'
-                 'kernels/fk/earth_topo_050714.tf'
-                 'kernels/pck/earth_000101_060525_060303.bpc'
-                 'kernels/lsk/naif0008.tls'
-                 'kernels/spk/ORMM__040501000000_00076XS.BSP'
-                 'kernels/pck/pck00008.tpc'
-                 'kernels/dsk/mars_lowres.bds'
-                           )
-
-      \begintext
-
-Solution Code
-
-.. code-block:: python
-       
-      #
-      # Solution visibl
-      #
-      from __future__ import print_function
-
-      #
-      # SpiceyPy package:
-      #
-      import spiceypy.utils.support_types as stypes
-      import spiceypy
-
-      def visibl():
-          #
-          # Local Parameters
-          #
-          METAKR = 'visibl.tm'
-          SCLKID = -82
-          TDBFMT = 'YYYY MON DD HR:MN:SC.### TDB ::TDB'
-          MAXIVL = 1000
-          MAXWIN = 2 * MAXIVL
-
-          #
-          # Load the meta-kernel.
-          #
-          spiceypy.furnsh( METAKR )
-
-          #
-          # Assign the inputs for our search.
-          #
-          # Since we're interested in the apparent location of the
-          # target, we use light time and stellar aberration
-          # corrections. We use the "converged Newtonian" form
-          # of the light time correction because this choice may
-          # increase the accuracy of the occultation times we'll
-          # compute using gfoclt.
-          #
-          srfpt  = 'DSS-14'
-          obsfrm = 'DSS-14_TOPO'
-          target = 'MEX'
-          abcorr = 'CN+S'
-          start  = '2004 MAY 2 TDB'
-          stop   = '2004 MAY 6 TDB'
-          elvlim =  6.0
-
-          #
-          # The elevation limit above has units of degrees; we convert
-          # this value to radians for computation using SPICE routines.
-          # We'll store the equivalent value in radians in revlim.
-          #
-          revlim = spiceypy.rpd() * elvlim
-
-          #
-          # We model the target shape as a point. We either model the
-          # blocking body's shape as an ellipsoid, or we represent
-          # its shape using actual topographic data. No body-fixed
-          # reference frame is required for the target since its
-          # orientation is not used.
-          #
-          back   = target
-          bshape = 'POINT'
-          bframe = ' '
-          front  = 'MARS'
-          fshape = 'ELLIPSOID'
-          fframe = 'IAU_MARS'
-
-          #
-          # The occultation type should be set to 'ANY' for a point
-          # target.
-          #
-          occtyp = 'any'
-
-          #
-          # Since SPICE doesn't directly support the AZ/EL coordinate
-          # system, we use the equivalent constraint
-          #
-          #    latitude > revlim
-          #
-          # in the latitudinal coordinate system, where the reference
-          # frame is topocentric and is centered at the viewing location.
-          #
-          crdsys = 'LATITUDINAL'
-          coord  = 'LATITUDE'
-          relate = '>'
-
-          #
-          # The adjustment value only applies to absolute extrema
-          # searches; simply give it an initial value of zero
-          # for this inequality search.
-          #
-          adjust = 0.0
-
-          #
-          # stepsz is the step size, measured in seconds, used to search
-          # for times bracketing a state transition. Since we don't expect
-          # any events of interest to be shorter than five minutes, and
-          # since the separation between events is well over 5 minutes,
-          # we'll use this value as our step size. Units are seconds.
-          #
-          stepsz = 300.0
-
-          #
-          # Display a banner for the output report:
-          #
-          print( '\n{:s}\n'.format(
-                 'Inputs for target visibility search:' )  )
-
-          print( '   Target                       = '
-                 '{:s}'.format( target )  )
-          print( '   Observation surface location = '
-                 '{:s}'.format( srfpt  )  )
-          print( '   Observer\'s reference frame   = '
-                 '{:s}'.format( obsfrm )  )
-          print( '   Blocking body                = '
-                 '{:s}'.format( front  )  )
-          print( '   Blocker\'s reference frame    = '
-                 '{:s}'.format( fframe )  )
-          print( '   Elevation limit (degrees)    = '
-                 '{:f}'.format( elvlim )  )
-          print( '   Aberration correction        = '
-                 '{:s}'.format( abcorr )  )
-          print( '   Step size (seconds)          = '
-                 '{:f}'.format( stepsz )  )
-
-          #
-          # Convert the start and stop times to ET.
-          #
-          etbeg = spiceypy.str2et( start )
-          etend = spiceypy.str2et( stop  )
-
-          #
-          # Display the search interval start and stop times
-          # using the format shown below.
-          #
-          #    2004 MAY 06 20:15:00.000 (TDB)
-          #
-          btmstr = spiceypy.timout( etbeg, TDBFMT )
-          print( '   Start time                   = '
-                 '{:s}'.format(btmstr) )
-
-          etmstr = spiceypy.timout( etend, TDBFMT )
-          print( '   Stop time                    = '
-                 '{:s}'.format(etmstr) )
-
-          print( ' ' )
-
-          #
-          # Initialize the "confinement" window with the interval
-          # over which we'll conduct the search.
-          #
-          cnfine = stypes.SPICEDOUBLE_CELL(2)
-          spiceypy.wninsd( etbeg, etend, cnfine )
-
-          #
-          # In the call below, the maximum number of window
-          # intervals gfposc can store internally is set to MAXIVL.
-          # We set the cell size to MAXWIN to achieve this.
-          #
-          riswin = stypes.SPICEDOUBLE_CELL( MAXWIN )
-
-          #
-          # Now search for the time period, within our confinement
-          # window, during which the apparent target has elevation
-          # at least equal to the elevation limit.
-          #
-          spiceypy.gfposc( target, obsfrm, abcorr, srfpt,
-                           crdsys, coord,  relate, revlim,
-                           adjust, stepsz, MAXIVL, cnfine, riswin )
-
-          #
-          # Now find the times when the apparent target is above
-          # the elevation limit and is not occulted by the
-          # blocking body (Mars). We'll find the window of times when
-          # the target is above the elevation limit and *is* occulted,
-          # then subtract that window from the view period window
-          # riswin found above.
-          #
-          # For this occultation search, we can use riswin as
-          # the confinement window because we're not interested in
-          # occultations that occur when the target is below the
-          # elevation limit.
-          #
-          # Find occultations within the view period window.
-          #
-          print( ' Searching using ellipsoid target shape model...' )
-
-          eocwin = stypes.SPICEDOUBLE_CELL( MAXWIN )
-
-          fshape = 'ELLIPSOID'
-
-          spiceypy.gfoclt( occtyp, front,  fshape,  fframe,
-                           back,   bshape, bframe,  abcorr,
-                           srfpt,  stepsz, riswin,  eocwin )
-          print( ' Done.' )
-
-          #
-          # Subtract the occultation window from the view period
-          # window: this yields the time periods when the target
-          # is visible.
-          #
-          evswin = spiceypy.wndifd( riswin, eocwin )
-
-          #
-          #  Repeat the search using low-resolution DSK data
-          # for the front body.
-          #
-          print( ' Searching using DSK target shape model...' )
-
-          docwin = stypes.SPICEDOUBLE_CELL( MAXWIN )
-
-          fshape = 'DSK/UNPRIORITIZED'
-
-          spiceypy.gfoclt( occtyp, front,  fshape,  fframe,
-                           back,   bshape, bframe,  abcorr,
-                           srfpt,  stepsz, riswin,  docwin )
-          print( ' Done.\n' )
-
-          dvswin = spiceypy.wndifd( riswin, docwin )
-
-          #
-          # The function wncard returns the number of intervals
-          # in a SPICE window.
-          #
-          winsiz = spiceypy.wncard( evswin )
-
-          if winsiz == 0:
-
-              print( 'No events were found.' )
-
-          else:
-              #
-              # Display the visibility time periods.
-              #
-              print( 'Visibility start and stop times of '
-                     '{0:s} as seen from {1:s}\n'
-                     'using both ellipsoidal and DSK '
-                     'target shape models:\n'.format(
-                         target, srfpt )                 )
-
-              for  i  in  range(winsiz):
-                  #
-                  # Fetch the start and stop times of
-                  # the ith interval from the ellipsoid
-                  # search result window evswin.
-                  #
-                  [intbeg, intend] = spiceypy.wnfetd( evswin, i )
-
-                  #
-                  # Convert the rise time to TDB calendar strings.
-                  # Write the results.
-                  #
-                  btmstr = spiceypy.timout( intbeg, TDBFMT )
-                  etmstr = spiceypy.timout( intend, TDBFMT )
-
-                  print( ' Ell: {:s} : {:s}'.format( btmstr, etmstr ) )
-
-                  #
-                  # Fetch the start and stop times of
-                  # the ith interval from the DSK
-                  # search result window dvswin.
-                  #
-                  [dintbg, dinten] = spiceypy.wnfetd( dvswin, i )
-
-                  #
-                  # Convert the rise time to TDB calendar strings.
-                  # Write the results.
-                  #
-                  btmstr = spiceypy.timout( dintbg, TDBFMT )
-                  etmstr = spiceypy.timout( dinten, TDBFMT )
-
-                  print( ' DSK: {:s} : {:s}\n'.format( btmstr, etmstr ) )
-              #
-              # End of result display loop.
-              #
-
-          spiceypy.unload( METAKR )
-
-      if __name__ == '__main__':
-          visibl()
-
-Solution Sample Output
+
+**Solution Sample Output**
 
 Numerical results shown for this example may differ across platforms
 since the results depend on the SPICE kernels used as input and on the
@@ -1081,12 +511,12 @@ Execute the program. The output is:
        DSK: 2004 MAY 05 17:17:32.375 TDB : 2004 MAY 05 23:54:06.221 TDB
 
 Extra Credit
-------------------------------
+------------
 
 In this "extra credit" section you will be presented with more
 complex tasks, aimed at improving your understanding of the geometry
-event finding subsystem and particularly the spiceypy.gfposc and
-spiceypy.gfdist functions.
+event finding subsystem and particularly the :py:func:`spiceypy.gfposc ` and
+:py:func:`spiceypy.gfdist ` functions.
 
 These "extra credit" tasks are provided as task statements, and
 unlike the regular tasks, no approach or solution source code is
@@ -1096,41 +526,29 @@ the questions asked in these tasks.
 Task statements
 ^^^^^^^^^^^^^^^
 
-.. code-block:: text
-
-       1.   Write a program that finds the times, within the time range
-
-            2004 MAY 2 TDB
-            2004 MAY 6 TDB
-
-            when the MEX spacecraft crosses Mars' equator. Display the
-            results using TDB calendar dates and millisecond precision.
+#. Write a program that finds the times, within the time range
+   ``2004 MAY 2 TDB`` to ``2004 MAY 6 TDB``,
+   when the MEX spacecraft crosses Mars' equator. Display the
+   results using TDB calendar dates and millisecond precision.
 
-       2.   Write a program that finds the times, within the time range
+#. Write a program that finds the times, within the time range
+   ``2004 MAY 2 TDB`` to ``2004 MAY 6 TDB``,
+   when the MEX spacecraft is at periapsis. Display the results
+   using TDB calendar dates and millisecond precision.
 
-            2004 MAY 2 TDB
-            2004 MAY 6 TDB
-
-            when the MEX spacecraft is at periapsis. Display the results
-            using TDB calendar dates and millisecond precision.
-
-       3.   Write a program that finds the times, within the time range
-
-            2004 MAY 2 TDB
-            2004 MAY 6 TDB
-
-            when the MEX spacecraft is at apoapsis. Display the results
-            using TDB calendar dates and millisecond precision.
+#. Write a program that finds the times, within the time range
+   ``2004 MAY 2 TDB`` to ``2004 MAY 6 TDB``,
+   when the MEX spacecraft is at apoapsis. Display the results
+   using TDB calendar dates and millisecond precision.
 
 Solutions
 ^^^^^^^^^
 
-.. code-block:: text
-
-       1.   Solution for the equator crossing search, using spiceypy.gfposc
-            for the MEX spacecraft latitude in the Mars body-fixed frame
-            equal to 0 degrees:
+#. Solution for the equator crossing search, using :py:func:`spiceypy.gfposc `
+   for the MEX spacecraft latitude in the Mars body-fixed frame
+   equal to 0 degrees:
 
+   .. code-block:: text
 
       Inputs for equator crossing search:
 
@@ -1170,9 +588,10 @@ Solutions
        Equator crossing time:           2004 MAY 05 16:25:58.350 (TDB)
        Equator crossing or stop time:   2004 MAY 05 17:39:23.889 (TDB)
 
-       2.   Solution for the periapsis search, using spiceypy.gfdist for
-            the MEX spacecraft distance from Mars at a local minimum:
+#. Solution for the periapsis search, using :py:func:`spiceypy.gfdist ` for
+   the MEX spacecraft distance from Mars at a local minimum:
 
+   .. code-block:: text
 
       Inputs for periapsis search:
 
@@ -1198,9 +617,10 @@ Solutions
        Periapsis time:                  2004 MAY 05 09:46:26.309 (TDB)
        Periapsis or end time:           2004 MAY 05 17:21:18.875 (TDB)
 
-       3.   Solution for the apoapsis search, using spiceypy.gfdist for the
-            MEX spacecraft distance from Mars at a local maximum:
+#. Solution for the apoapsis search, using :py:func:`spiceypy.gfdist ` for the
+   MEX spacecraft distance from Mars at a local maximum:
 
+   .. code-block:: text
 
       Inputs for apoapsis search:
 
diff --git a/docs/insitu_sensing.rst b/docs/insitu_sensing.rst
index 55e36ad9..b42a7812 100644
--- a/docs/insitu_sensing.rst
+++ b/docs/insitu_sensing.rst
@@ -1,5 +1,5 @@
 In-situ Sensing Hands-On Lesson, using CASSINI
-===============================================
+==============================================
 
 November 20, 2017
 
@@ -41,10 +41,10 @@ this lesson:
 
 These tutorials are available from the NAIF ftp server at JPL:
 
-`https://naif.jpl.nasa.gov/naif/tutorials.html `_
+https://naif.jpl.nasa.gov/naif/tutorials.html
 
 Required Readings
-^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Required Readings `_ are also available on the NAIF website at:
@@ -65,7 +65,7 @@ installation tree.
       time.req         UTC to ET time conversion
 
 The Permuted Index
-^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Permuted Index `_ is also available on the NAIF website at:
@@ -80,23 +80,33 @@ discover which SpiceyPy functions perform functions of interest, as well
 as the names of the source files that contain these functions.
 
 SpiceyPy API Documentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A SpiceyPy function's parameters specification is available using the
 built-in Python help system.
 
 For example, the Python help function
 
-.. code-block:: python
+.. py-editor::
+    :env: isenv
+    :config: pyscript_insitu_sensing.json
+    :setup:
+
+    import spiceypy
 
-     import spiceypy
-     help(spiceypy.str2et)
+.. py-editor::
+    :env: isenv
+    :config: pyscript_insitu_sensing.json
 
-describes of the str2et function's parameters, while the document
+    import spiceypy
+    
+    help(spiceypy.str2et)
 
-`https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html `_
+describes the ``str2et`` function's parameters, while the document
 
-describes extensively the str2et functionality.
+https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html
+
+describes extensively the ``str2et`` functionality.
 
 Kernels Used
 ------------
@@ -120,7 +130,7 @@ The following kernels are used in examples provided in this lesson:
 These SPICE kernels are included in the lesson package available from
 the NAIF server at JPL:
 
-`http://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/ `_
+http://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/
 
 
 SpiceyPy Modules Used
@@ -179,61 +189,59 @@ their corresponding CSPICE versions for detailed interface
 specifications.
 
 Step-1: "UTC to ET"
-------------------------------
+-------------------
 
 "UTC to ET" Task Statement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Write a program that computes and prints the Ephemeris Time (ET)
 corresponding to "2004-06-11T19:32:00" UTC, as the number of
 ephemeris seconds past J2000, .
 
 "UTC to ET" Hints
-^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^
 
-Find out what SPICE kernel(s) is(are) needed to support this conversion.
-Reference the "time.req" and/or "Time" tutorial.
+#. Find out what SPICE kernel(s) is(are) needed to support this
+   conversion. Reference the "time.req" and/or "Time" tutorial.
 
-Find necessary kernel(s) on the NAIF's FTP site.
+#. Find necessary kernel(s) on the NAIF's FTP site.
 
-Find out what routine should be called to load necessary kernel(s).
-Reference the "kernel.req" and/or "Loading Kernels" tutorial.
+#. Find out what routine should be called to load necessary kernel(s).
+   Reference the "kernel.req" and/or "Loading Kernels" tutorial.
 
-Find the
-"loader" routine calling sequence specification. Look at the "time.req"and
-that routine's source code header. This routine may be an entry point,
-in which case there will be no source file with the same name. To find
-out in which source file this entry point is, search for its name in the
-"Permuted Index".
+#. Find the "loader" routine calling sequence specification. Look at
+   the "time.req" and that routine's source code header. This routine
+   may be an entry point, in which case there will be no source file
+   with the same name. To find out in which source file this entry
+   point is, search for its name in the "Permuted Index".
 
-Find the routine(s) used to convert time between UTC and ET. Look at the
-"time.req" and/or "Time" tutorial.
+#. Find the routine(s) used to convert time between UTC and ET. Look
+   at the "time.req" and/or "Time" tutorial.
 
-Find the
-"converter" routine(s) calling sequence specification. Look in the "time.req"
-and the routine's source code header.
+#. Find the "converter" routine(s) calling sequence specification.
+   Look in the "time.req" and the routine's source code header.
 
-Put all calls together in a program, add variable declarations (the
-routine header's "Declarations" and "Examples" sections are a good
-place to look for declaration specification and examples) and output
-print statements.
+#. Put all calls together in a program, add variable declarations (the
+   routine header's "Declarations" and "Examples" sections are a good
+   place to look for declaration specification and examples) and output
+   print statements.
 
 "UTC to ET" Solution Steps
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Only one kernel file is needed to support this conversion – an LSK file
 "naif0008.tls".
 
-As any other SPICE kernel this file can be loaded by the spiceypy.furnsh
+As any other SPICE kernel this file can be loaded by the :py:func:`spiceypy.furnsh `
 function. For that, the name of the file can be provided as a sole
 argument of this routine:
 
-.. code-block:: python
-
-      ...
-      lskfile = 'naif0008.tls'
+.. py-editor::
+    :env: isenv
+    
+    lskfile = 'kernels/lsk/naif0008.tls'
 
-      spiceypy.furnsh(lskfile)
+    spiceypy.furnsh(lskfile)
 
 or it can be listed in a meta-kernel:
 
@@ -256,9 +264,10 @@ or it can be listed in a meta-kernel:
       \begintext
 
 the name of which, let's call it "convrt.tm", can be then provided as
-a sole argument of the :py:func:`spiceypy.spiceypy.furnsh` routine:
+a sole argument of the :py:func:`spiceypy.furnsh ` routine:
 
-.. code-block:: python
+.. py-editor::
+    :env: isenv
 
           mkfile = 'convrt.tm'
           spiceypy.furnsh(mkfile)
@@ -270,12 +279,12 @@ simply adding more kernels to the list in KERNEL_TO_LOAD without
 changing the program code will accomplish that.
 
 The highest level SpiceyPy time routine converting UTC to ET is
-spiceypy.str2et :py:func:`spiceypy.spiceypy.str2et` .
+:py:func:`spiceypy.str2et `.
 
 It has two arguments – input time string representing UTC in a variety
-of formats (see :py:func:`spiceypy.spiceypy.str2et` header's section "Particulars" for
+of formats (see :py:func:`spiceypy.str2et ` header's section "Particulars" for
 the complete description of input time formats) and output DP number of
-ET seconds past J2000. A call to spiceypy.str2et converting a given UTC
+ET seconds past J2000. A call to :py:func:`spiceypy.str2et ` converting a given UTC
 to ET could look like this:
 
 .. code-block:: python
@@ -283,7 +292,7 @@ to ET could look like this:
           utc =  '2004-06-11T19:32:00'
           et = spiceypy.str2et(utc)
 
-By combining :py:func:`spiceypy.spiceypy.furnsh` and :py:func:`spiceypy.spiceypy.str2et` calls and required
+By combining :py:func:`spiceypy.furnsh ` and :py:func:`spiceypy.str2et ` calls and required
 declarations and by adding a simple print statement, one would get a
 complete program that prints ET for the given UTC epoch.
 
@@ -301,90 +310,59 @@ output:
       ET        =     140254384.184625
 
 "UTC to ET" Code
-^^^^^^^^^^^^^^^^^
-
-Program "convrt.py":
-
-.. code-block:: python
-
-      from __future__ import print_function
-      import spiceypy
-
-      def convrt():
-
-          mkfile = 'convrt.tm'
-          spiceypy.furnsh(mkfile)
-
-          utc =  '2004-06-11T19:32:00'
-          et = spiceypy.str2et(utc)
-
-          print('UTC       = {:s}'.format(utc))
-          print('ET        = {:20.6f}'.format(et))
-
-          spiceypy.unload(mkfile)
-
-
-      if __name__ == '__main__':
-          convrt()
+^^^^^^^^^^^^^^^^
 
 Meta-kernel file "convrt.tm":
 
-.. code-block:: text
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/convrt_make_mk.py
 
-      KPL/MK
 
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
+Program "convrt.py":
 
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/convrt.py
 
-         File Name                   Description
-         --------------------------  ----------------------------------
-         naif0008.tls                Generic LSK.
-
-      \begindata
-         KERNELS_TO_LOAD = (
-                           'kernels/lsk/naif0008.tls'
-                           )
-      \begintext
 
 Step-2: "SCLK to ET"
-------------------------------
+--------------------
 
 "SCLK to ET" Task Statement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Extend the program from Step-1 to compute and print ET for the following
 CASSINI on-board clock epoch "1465674964.105".
 
 "SCLK to ET" Hints
-^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
 
-Find out what additional (to those already loaded in Step-1) SPICE
-kernel(s) is(are) needed to support SCLK to ET conversion. Look at the
-"sclk.req" and/or "SCLK" tutorial.
+#. Find out what additional (to those already loaded in Step-1) SPICE
+   kernel(s) is(are) needed to support SCLK to ET conversion. Look at
+   the "sclk.req" and/or "SCLK" tutorial.
 
-Find necessary kernel(s) on the NAIF's FTP site.
+#. Find necessary kernel(s) on the NAIF's FTP site.
 
-Modify the program or meta-kernel to load this (these) kernels.
+#. Modify the program or meta-kernel to load this (these) kernels.
 
-Find the routine(s) needed to convert time between SCLK and ET. Look at
-the "sclk.req" and/or "Time" and "SCLK" tutorials.
+#. Find the routine(s) needed to convert time between SCLK and ET.
+   Look at the "sclk.req" and/or "Time" and "SCLK" tutorials.
 
-Find the
-"converter" routine's calling sequence specification. Look in the "sclk.req"
-and the routine's source code header.
+#. Find the "converter" routine's calling sequence specification. Look
+   in the "sclk.req" and the routine's source code header.
 
-Look at "naif_ids.req" and the comments in the additional kernel(s)
-that you have loaded for information on proper values of input arguments
-of this routine.
+#. Look at "naif_ids.req" and the comments in the additional kernel(s)
+   that you have loaded for information on proper values of input
+   arguments of this routine.
 
-Add calls to the
-"converter" routine(s), necessary variable declarations (the routine header's" Declarations"and
-"Examples" sections are a good place to look for declaration
-specification and examples), and output print statements to the program.
+#. Add calls to the "converter" routine(s), necessary variable
+   declarations (the routine header's "Declarations" and "Examples"
+   sections are a good place to look for declaration specification and
+   examples), and output print statements to the program.
 
 "SCLK to ET" Solution Steps
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A CASSINI SCLK file is needed additionally to the LSK file loaded in the
 Step-1 to support this conversion.
@@ -415,12 +393,12 @@ variable:
       \begintext
 
 The highest level SpiceyPy routine converting SCLK to ET is
-spiceypy.scs2e :py:func:`spiceypy.spiceypy.scs2e` .
+:py:func:`spiceypy.scs2e `.
 
 It has three arguments – NAIF ID for CASSINI s/c (-82 as described by
 "naif_ids.req" document), input time string representing CASSINI
 SCLK, and output DP number of ET seconds past J2000. A call to
-spiceypy.str2et converting given SCLK to ET could look like this:
+:py:func:`spiceypy.str2et ` converting given SCLK to ET could look like this:
 
 .. code-block:: python
 
@@ -428,7 +406,7 @@ spiceypy.str2et converting given SCLK to ET could look like this:
           sclk = '1465674964.105'
           et = spiceypy.scs2e(scid, sclk)
 
-By adding the spiceypy.scs2e call, required declarations and a simple
+By adding the :py:func:`spiceypy.scs2e ` call, required declarations and a simple
 print statement, one would get a complete program that prints ET for the
 given SCLK epoch.
 
@@ -444,101 +422,61 @@ output:
       ET        =     140254384.183426
 
 "SCLK to ET" Code
-^^^^^^^^^^^^^^^^^^^^
-
-Program "sclket.py":
-
-.. code-block:: python
-
-      from __future__ import print_function
-      import spiceypy
-
-      def sclket():
-
-          mkfile = 'sclket.tm'
-          spiceypy.furnsh(mkfile)
-
-          utc =  '2004-06-11T19:32:00'
-          et = spiceypy.str2et(utc)
-
-          print('UTC       = {:s}'.format(utc))
-          print('ET        = {:20.6f}'.format(et))
-
-          scid = -82
-          sclk = '1465674964.105'
-          et = spiceypy.scs2e(scid, sclk)
-
-          print('SCLK      = {:s}'.format(sclk))
-          print('ET        = {:20.6f}'.format(et))
-
-          spiceypy.unload(mkfile)
-
-
-      if __name__ == '__main__':
-          sclket()
+^^^^^^^^^^^^^^^^^
 
 Meta-kernel file "sclket.tm":
 
-.. code-block:: text
-
-      KPL/MK
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/sclket_make_mk.py
 
+Program "sclket.py":
 
-         File Name                   Description
-         --------------------------  ----------------------------------
-         naif0008.tls                Generic LSK.
-         cas00084.tsc                Cassini SCLK.
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/sclket.py
 
-      \begindata
-         KERNELS_TO_LOAD = (
-                           'kernels/lsk/naif0008.tls'
-                           'kernels/sclk/cas00084.tsc'
-                           )
-      \begintext
 
 Step-3: "Spacecraft State"
-------------------------------
+--------------------------
 
 "Spacecraft State" Task Statement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Extend the program from Step-2 to compute geometric state – position and
 velocity – of the CASSINI spacecraft with respect to the Sun in the
 Ecliptic frame at the epoch specified by SCLK time from Step-2.
 
 "Spacecraft State" Hints
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^
 
-Find out what additional (to those already loaded in Steps-1&2) SPICE
-kernel(s) is(are) needed to support state computation. Look at the
-"spk.req" and/or "SPK" tutorial.
+#. Find out what additional (to those already loaded in Steps-1&2)
+   SPICE kernel(s) is(are) needed to support state computation. Look
+   at the "spk.req" and/or "SPK" tutorial.
 
-Find necessary kernel(s) on the NAIF's FTP site.
+#. Find necessary kernel(s) on the NAIF's FTP site.
 
-Verify that the kernels contain enough data to compute the state of
-interest. Use "brief" utility program located under "toolkit/exe"
-directory for that.
+#. Verify that the kernels contain enough data to compute the state of
+   interest. Use "brief" utility program located under "toolkit/exe"
+   directory for that.
 
-Modify the meta-kernel to load this(these) kernels.
+#. Modify the meta-kernel to load this(these) kernels.
 
-Determine the routine(s) needed to compute states. Look at the
-"spk.req" and/or "SPK" tutorial presentation.
+#. Determine the routine(s) needed to compute states. Look at the
+   "spk.req" and/or "SPK" tutorial presentation.
 
-Find the the routine(s) calling sequence specification. Look in the
-"spk.req" and the routine's source code header.
+#. Find the routine(s) calling sequence specification. Look in the
+   "spk.req" and the routine's source code header.
 
-Reference the "naif_ids.req" and "frames.req"and the routine(s)
-header "Inputs" and "Particulars" sections to determine proper
-values of the input arguments of this routine.
+#. Reference the "naif_ids.req" and "frames.req" and the routine(s)
+   header "Inputs" and "Particulars" sections to determine proper
+   values of the input arguments of this routine.
 
-Add calls to the routine(s), necessary variable declarations and output
-print statements to the program.
+#. Add calls to the routine(s), necessary variable declarations and
+   output print statements to the program.
 
 "Spacecraft State" Solution Steps
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A CASSINI spacecraft trajectory SPK and generic planetary ephemeris SPK
 files are needed to support computation of the state of interest.
@@ -574,8 +512,8 @@ the program:
                            )
       \begintext
 
-The highest level SpiceyPy routine computing states is spiceypy.spkezr
-:py:func:`spiceypy.spiceypy.spkezr` .
+The highest level SpiceyPy routine computing states is
+:py:func:`spiceypy.spkezr `.
 
 We are interested in computing CASSINI position and velocity with
 respect to the Sun, therefore the target and observer names should be
@@ -588,7 +526,7 @@ in which the state should be computed is 'ECLIPJ2000' (see
 
 Since we need only the geometric position, the 'abcorr' argument of the
 routine should be set to 'NONE' (see aberration correction discussion in
-the :py:func:`spiceypy.spiceypy.spkezr` .
+the :py:func:`spiceypy.spkezr `.
 
 Putting it all together, we get:
 
@@ -620,117 +558,57 @@ output:
       VZ        =             0.040603
 
 "Spacecraft State" Code
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Program "getsta.py":
-
-.. code-block:: python
-
-      from __future__ import print_function
-      import spiceypy
-
-      def getsta():
-
-          mkfile = 'getsta.tm'
-          spiceypy.furnsh(mkfile)
-
-          utc =  '2004-06-11T19:32:00'
-          et = spiceypy.str2et(utc)
-
-          print('UTC       = {:s}'.format(utc))
-          print('ET        = {:20.6f}'.format(et))
-
-          scid = -82
-          sclk = '1465674964.105'
-          et = spiceypy.scs2e(scid, sclk)
-
-          print('SCLK      = {:s}'.format(sclk))
-          print('ET        = {:20.6f}'.format(et))
-
-          target = 'CASSINI'
-          frame  = 'ECLIPJ2000'
-          corrtn = 'NONE'
-          observ = 'SUN'
-
-          state, ltime = spiceypy.spkezr(target, et, frame,
-                                         corrtn, observ)
-
-          print(' X        = {:20.6f}'.format(state[0]))
-          print(' Y        = {:20.6f}'.format(state[1]))
-          print(' Z        = {:20.6f}'.format(state[2]))
-          print('VX        = {:20.6f}'.format(state[3]))
-          print('VY        = {:20.6f}'.format(state[4]))
-          print('VZ        = {:20.6f}'.format(state[5]))
-
-          spiceypy.unload(mkfile)
-
-
-      if __name__ == '__main__':
-          getsta()
+^^^^^^^^^^^^^^^^^^^^^^^
 
 Meta-kernel file "getsta.tm":
 
-.. code-block:: text
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/getsta_make_mk.py
+    
+    
+Program "getsta.py":
 
-      KPL/MK
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/getsta.py
 
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
 
 
-         File Name                   Description
-         --------------------------  ----------------------------------
-         naif0008.tls                Generic LSK.
-         cas00084.tsc                Cassini SCLK.
-         020514_SE_SAT105.bsp        Saturnian Satellite Ephemeris SPK.
-         030201AP_SK_SM546_T45.bsp   Cassini Spacecraft SPK.
-         981005_PLTEPH-DE405S.bsp    Planetary Ephemeris SPK.
-         sat128.bsp                  Saturnian Satellite Ephemeris SPK.
-
-      \begindata
-         KERNELS_TO_LOAD = (
-                           'kernels/lsk/naif0008.tls'
-                           'kernels/sclk/cas00084.tsc'
-                           'kernels/spk/020514_SE_SAT105.bsp'
-                           'kernels/spk/030201AP_SK_SM546_T45.bsp'
-                           'kernels/spk/981005_PLTEPH-DE405S.bsp'
-                           'kernels/spk/sat128.bsp'
-                           )
-      \begintext
-
 Step-4: "Sun Direction"
-------------------------------
+-----------------------
 
 "Sun Direction" Task Statement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Extend the program from Step-3 to compute apparent direction of the Sun
 in the INMS frame at the epoch specified by SCLK time from Step-2.
 
 "Sun Direction" Hints
-^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^
 
-Determine the additional SPICE kernels needed to support the direction
-computation, knowing that they should provide the s/c and instrument
-frame orientation. Retrieve these kernels from the NAIF's FTP site.
+#. Determine the additional SPICE kernels needed to support the
+   direction computation, knowing that they should provide the s/c and
+   instrument frame orientation. Retrieve these kernels from the
+   NAIF's FTP site.
 
-Verify that the orientation data in the kernels have adequate coverage
-to support computation of the direction of interest. Use
-"ckbrief" utility program located under" toolkit/exe" directory
-for that.
+#. Verify that the orientation data in the kernels have adequate
+   coverage to support computation of the direction of interest. Use
+   "ckbrief" utility program located under "toolkit/exe" directory
+   for that.
 
-Modify the meta-kernel to load this(these) kernels.
+#. Modify the meta-kernel to load this(these) kernels.
 
-Determine the proper input arguments for the spiceypy.spkpos call to
-calculate the direction (which is the position portion of the output
-state). Look through the Frames Kernel find the name of the frame to
-used.
+#. Determine the proper input arguments for the :py:func:`spiceypy.spkpos `
+   call to calculate the direction (which is the position portion of
+   the output state). Look through the Frames Kernel to find the name
+   of the frame to use.
 
-Add calls to the routine(s), necessary variable declarations and output
-print statements to the program.
+#. Add calls to the routine(s), necessary variable declarations and
+   output print statements to the program.
 
 "Sun Direction" Solution Steps
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 A CASSINI spacecraft orientation CK file, providing s/c orientation with
 respect to an inertial frame, and CASSINI FK file, providing orientation
@@ -774,7 +652,7 @@ the program:
       \begintext
 
 The same highest level SpiceyPy routine computing positions,
-spiceypy.spkpos, can be used to compute this direction.
+:py:func:`spiceypy.spkpos `, can be used to compute this direction.
 
 Since this is the direction of the Sun as seen from the s/c, the target
 argument should be set to 'Sun' and the observer argument should be set
@@ -786,7 +664,7 @@ Since the apparent, or 'as seen', position is sought for, the 'abcorr'
 argument of the routine should be set to 'LT+S' (see aberration correction discussion in the ("\cspice/src/cspice/spkpos_c.c")
 
 If desired, the position can then be turned into a unit vector using
-spiceypy.vhat function
+:py:func:`spiceypy.vhat ` function
 (https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.vhat).
 Putting it all together, we get:
 
@@ -822,147 +700,70 @@ output:
       SUNDIR(Z) =             0.372167
 
 "Sun Direction" Code
-^^^^^^^^^^^^^^^^^^^^^^
-
-Program "soldir.py":
-
-.. code-block:: python
-
-      from __future__ import print_function
-      import spiceypy
-
-      def soldir():
-
-          mkfile = 'soldir.tm'
-          spiceypy.furnsh(mkfile)
-
-          utc =  '2004-06-11T19:32:00'
-          et = spiceypy.str2et(utc)
-
-          print('UTC       = {:s}'.format(utc))
-          print('ET        = {:20.6f}'.format(et))
-
-          scid = -82
-          sclk = '1465674964.105'
-          et = spiceypy.scs2e(scid, sclk)
-
-          print('SCLK      = {:s}'.format(sclk))
-          print('ET        = {:20.6f}'.format(et))
-
-          target = 'CASSINI'
-          frame  = 'ECLIPJ2000'
-          corrtn = 'NONE'
-          observ = 'SUN'
-
-          state, ltime = spiceypy.spkezr(target, et, frame,
-                                         corrtn, observ)
-
-          print(' X        = {:20.6f}'.format(state[0]))
-          print(' Y        = {:20.6f}'.format(state[1]))
-          print(' Z        = {:20.6f}'.format(state[2]))
-          print('VX        = {:20.6f}'.format(state[3]))
-          print('VY        = {:20.6f}'.format(state[4]))
-          print('VZ        = {:20.6f}'.format(state[5]))
-
-          target = 'SUN'
-          frame  = 'CASSINI_INMS'
-          corrtn = 'LT+S'
-          observ = 'CASSINI'
-
-          sundir, ltime = spiceypy.spkpos(target, et, frame,
-                                          corrtn, observ)
-          sundir = spiceypy.vhat(sundir)
-
-          print('SUNDIR(X) = {:20.6f}'.format(sundir[0]))
-          print('SUNDIR(Y) = {:20.6f}'.format(sundir[1]))
-          print('SUNDIR(Z) = {:20.6f}'.format(sundir[2]))
-
-          spiceypy.unload(mkfile)
-
-
-      if __name__ == '__main__':
-          soldir()
+^^^^^^^^^^^^^^^^^^^^
 
 Meta-kernel file "soldir.tm":
 
-.. code-block:: text
-
-      KPL/MK
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/soldir_make_mk.py
 
+Program "soldir.py":
 
-         File Name                   Description
-         --------------------------  ----------------------------------
-         naif0008.tls                Generic LSK.
-         cas00084.tsc                Cassini SCLK.
-         020514_SE_SAT105.bsp        Saturnian Satellite Ephemeris SPK.
-         030201AP_SK_SM546_T45.bsp   Cassini Spacecraft SPK.
-         981005_PLTEPH-DE405S.bsp    Planetary Ephemeris SPK.
-         sat128.bsp                  Saturnian Satellite Ephemeris SPK.
-         04135_04171pc_psiv2.bc      Cassini Spacecraft CK.
-         cas_v37.tf                  Cassini FK.
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/soldir.py
 
 
-      \begindata
-         KERNELS_TO_LOAD = (
-                           'kernels/lsk/naif0008.tls'
-                           'kernels/sclk/cas00084.tsc'
-                           'kernels/spk/020514_SE_SAT105.bsp'
-                           'kernels/spk/030201AP_SK_SM546_T45.bsp'
-                           'kernels/spk/981005_PLTEPH-DE405S.bsp'
-                           'kernels/spk/sat128.bsp'
-                           'kernels/ck/04135_04171pc_psiv2.bc'
-                           'kernels/fk/cas_v37.tf'
-                           )
-      \begintext
-
 Step-5: "Sub-Spacecraft Point"
 ------------------------------
 
 "Sub-Spacecraft Point" Task Statement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Extend the program from Step-4 to compute planetocentric longitude and
-and latitude of the sub-spacecraft point on Phoebe, and the direction
+latitude of the sub-spacecraft point on Phoebe, and the direction
 from the spacecraft to that point in the INMS frame.
 
 "Sub-Spacecraft Point" Hints
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Find the SpiceyPy routine that computes sub-observer point coordinates.
-Use "Most Used SpiceyPy APIs" or" subpt" cookbook program for that.
+#. Find the SpiceyPy routine that computes sub-observer point
+   coordinates. Use "Most Used SpiceyPy APIs" or "subpt" cookbook
+   program for that.
 
-Refer to the routine's header to determine the additional kernels needed
-for this direction computation. Get these kernels from the NAIF's FTP
-site. Modify the meta-kernel to load this(these) kernels.
+#. Refer to the routine's header to determine the additional kernels
+   needed for this direction computation. Get these kernels from the
+   NAIF's FTP site. Modify the meta-kernel to load this(these)
+   kernels.
 
-Determine the proper input arguments for the routine. Refer to the
-routine's header for that information.
+#. Determine the proper input arguments for the routine. Refer to the
+   routine's header for that information.
 
-Convert the surface point Cartesian vector returned by this routine to
-latitudinal coordinates. Use "Permuted Index" to find the routine
-that does this conversion. Refer to the routine's header for
-input/output argument specifications.
+#. Convert the surface point Cartesian vector returned by this routine
+   to latitudinal coordinates. Use "Permuted Index" to find the
+   routine that does this conversion. Refer to the routine's header
+   for input/output argument specifications.
 
-Since the Cartesian vector from the spacecraft to the sub-spacecraft
-point is computed in the Phoebe body-fixed frame, it should be
-transformed into the instrument frame get the direction we are looking
-for. Refer to "frames.req" and/or" Frames" tutorial to determine
-the name of the routine computing transformations and use it to compute
-transformation from Phoebe body-fixed to the INMS frame.
+#. Since the Cartesian vector from the spacecraft to the sub-spacecraft
+   point is computed in the Phoebe body-fixed frame, it should be
+   transformed into the instrument frame to get the direction we are
+   looking for. Refer to "frames.req" and/or "Frames" tutorial to
+   determine the name of the routine computing transformations and use
+   it to compute transformation from Phoebe body-fixed to the INMS
+   frame.
 
-Using "Permuted Index" find the routine that multiplies 3x3 matrix by
-3d vector and use it to rotate the vector to the instrument frame.
+#. Using "Permuted Index" find the routine that multiplies 3x3 matrix
+   by 3d vector and use it to rotate the vector to the instrument
+   frame.
 
-Add calls to the routine(s), necessary variable declarations and output
-print statements to the program.
+#. Add calls to the routine(s), necessary variable declarations and
+   output print statements to the program.
 
 "Sub-Spacecraft Point" Solution Steps
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-The :py:func:`spiceypy.spiceypy.subpnt` routine can be
+The :py:func:`spiceypy.subpnt ` routine can be
 used to compute the sub-observer point and the vector from the observer
 to that point with a single call. To determine this point as the closest point on the Phoebe ellipsoid, the 'method'
 argument has to be set to 'NEAR POINT: ELLIPSOID'. For our case the
@@ -973,7 +774,7 @@ Since the s/c is close to Phoebe, light time does not need to be taken
 into account and, therefore, the 'abcorr' argument can be set to
 'NONE'.
 
-In order for spiceypy.subpnt to compute the nearest point location, a
+In order for :py:func:`spiceypy.subpnt ` to compute the nearest point location, a
 PCK file containing Phoebe radii has to be loaded into the program (see
 "Files" section of the routine's header.) All other files required
 for this computation are already being loaded by the program. With PCK
@@ -1015,28 +816,28 @@ file name added to it, the updated meta-kernel will look like this:
       \begintext
 
 The sub-spacecraft point Cartesian vector can be converted to
-planetocentric radius, longitude and latitude using the spiceypy.reclat
-routine :py:func:`spiceypy.spiceypy.reclat` .
+planetocentric radius, longitude and latitude using the
+:py:func:`spiceypy.reclat ` routine.
 
 The vector from the spacecraft to the sub-spacecraft point returned by
-spiceypy.subpnt has to be rotated from the body-fixed frame to the
+:py:func:`spiceypy.subpnt ` has to be rotated from the body-fixed frame to the
 instrument frame. The name of the routine that computes 3x3 matrices
-rotating vectors from one frame to another is spiceypy.pxform
-:py:func:`spiceypy.spiceypy.pxform` .
+rotating vectors from one frame to another is
+:py:func:`spiceypy.pxform `.
 
 In our case the
 "from' argument should be set to 'IAU_PHOEBE' and the 'to' argument
 should be set to 'CASSINI_INMS'
 
 The vector should be then multiplied by this matrix to rotate it to the
-instrument frame. The spiceypy.mxv routine performs that function :py:func:`spiceypy.spiceypy.mxv` .
+instrument frame. The :py:func:`spiceypy.mxv ` routine performs that function.
 
 After applying the rotation, normalize the resultant vector using the
-spiceypy.vhat function.
+:py:func:`spiceypy.vhat ` function.
 
-For output the longitude and latitude angles returned by spiceypy.reclat
-in radians can be converted to degrees by multiplying by spiceypy.dpr
-function :py:func:`spiceypy.spiceypy.dpr` .
+For output the longitude and latitude angles returned by :py:func:`spiceypy.reclat `
+in radians can be converted to degrees by multiplying by the
+:py:func:`spiceypy.dpr ` function.
 
 Putting it all together, we get:
 
@@ -1090,174 +891,69 @@ output:
       SBPDIR(Z) =            -0.015905
 
 "Sub-Spacecraft Point" Code
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Program
-
-::
-
-      from __future__ import print_function
-      import spiceypy
-
-      def sscpnt():
-
-          mkfile = 'sscpnt.tm'
-          spiceypy.furnsh(mkfile)
-
-          utc =  '2004-06-11T19:32:00'
-          et = spiceypy.str2et(utc)
-
-          print('UTC       = {:s}'.format(utc))
-          print('ET        = {:20.6f}'.format(et))
-
-          scid = -82
-          sclk = '1465674964.105'
-          et = spiceypy.scs2e(scid, sclk)
-
-          print('SCLK      = {:s}'.format(sclk))
-          print('ET        = {:20.6f}'.format(et))
-
-          target = 'CASSINI'
-          frame  = 'ECLIPJ2000'
-          corrtn = 'NONE'
-          observ = 'SUN'
-
-          state, ltime = spiceypy.spkezr(target, et, frame,
-                                         corrtn, observ)
-
-          print(' X        = {:20.6f}'.format(state[0]))
-          print(' Y        = {:20.6f}'.format(state[1]))
-          print(' Z        = {:20.6f}'.format(state[2]))
-          print('VX        = {:20.6f}'.format(state[3]))
-          print('VY        = {:20.6f}'.format(state[4]))
-          print('VZ        = {:20.6f}'.format(state[5]))
-
-          target = 'SUN'
-          frame  = 'CASSINI_INMS'
-          corrtn = 'LT+S'
-          observ = 'CASSINI'
-
-          sundir, ltime = spiceypy.spkpos(target, et, frame,
-                                          corrtn, observ)
-          sundir = spiceypy.vhat(sundir)
-
-          print('SUNDIR(X) = {:20.6f}'.format(sundir[0]))
-          print('SUNDIR(Y) = {:20.6f}'.format(sundir[1]))
-          print('SUNDIR(Z) = {:20.6f}'.format(sundir[2]))
-
-          method = 'NEAR POINT: ELLIPSOID'
-          target = 'PHOEBE'
-          frame  = 'IAU_PHOEBE'
-          corrtn = 'NONE'
-          observ = 'CASSINI'
-
-          spoint, trgepc, srfvec = spiceypy.subpnt(method, target, et,
-                                                   frame, corrtn, observ)
-
-          srad, slon, slat = spiceypy.reclat(spoint)
-
-          fromfr = 'IAU_PHOEBE'
-          tofr   = 'CASSINI_INMS'
-
-          m2imat = spiceypy.pxform(fromfr, tofr, et)
-
-          sbpdir = spiceypy.mxv(m2imat, srfvec)
-          sbpdir = spiceypy.vhat(sbpdir)
-
-          print('LON       = {:20.6f}'.format(slon * spiceypy.dpr()))
-          print('LAT       = {:20.6f}'.format(slat * spiceypy.dpr()))
-          print('SBPDIR(X) = {:20.6f}'.format(sbpdir[0]))
-          print('SBPDIR(Y) = {:20.6f}'.format(sbpdir[1]))
-          print('SBPDIR(Z) = {:20.6f}'.format(sbpdir[2]))
-
-          spiceypy.unload(mkfile)
-
-
-      if __name__ == '__main__':
-          sscpnt()
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Meta-kernel file "sscpnt.tm":
 
-::
-
-      KPL/MK
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
-
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/sscpnt_make_mk.py
+    
+Program "sscpnt.py":
 
-         File Name                   Description
-         --------------------------  ----------------------------------
-         naif0008.tls                Generic LSK.
-         cas00084.tsc                Cassini SCLK.
-         020514_SE_SAT105.bsp        Saturnian Satellite Ephemeris SPK.
-         030201AP_SK_SM546_T45.bsp   Cassini Spacecraft SPK.
-         981005_PLTEPH-DE405S.bsp    Planetary Ephemeris SPK.
-         sat128.bsp                  Saturnian Satellite Ephemeris SPK.
-         04135_04171pc_psiv2.bc      Cassini Spacecraft CK.
-         cas_v37.tf                  Cassini FK.
-         cpck05Mar2004.tpc           Cassini project PCK.
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/sscpnt.py
 
 
-      \begindata
-         KERNELS_TO_LOAD = (
-                           'kernels/lsk/naif0008.tls'
-                           'kernels/sclk/cas00084.tsc'
-                           'kernels/spk/020514_SE_SAT105.bsp'
-                           'kernels/spk/030201AP_SK_SM546_T45.bsp'
-                           'kernels/spk/981005_PLTEPH-DE405S.bsp'
-                           'kernels/spk/sat128.bsp'
-                           'kernels/ck/04135_04171pc_psiv2.bc'
-                           'kernels/fk/cas_v37.tf'
-                           'kernels/pck/cpck05Mar2004.tpc'
-                           )
-      \begintext
 
 Step-6: "Spacecraft Velocity"
-------------------------------
+-----------------------------
 
 "Spacecraft Velocity" Task Statement
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Extend the program from Step-5 to compute the spacecraft velocity with
 respect to Phoebe in the INMS frame.
 
 "Spacecraft Velocity" Hints
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Compute velocity of the spacecraft with respect to Phoebe in some
-inertial frame, for example J2000. Recall that velocity is the last
-three components of the state vector returned by spiceypy.spkezr.
+#. Compute velocity of the spacecraft with respect to Phoebe in some
+   inertial frame, for example J2000. Recall that velocity is the last
+   three components of the state vector returned by
+   :py:func:`spiceypy.spkezr `.
 
-Since the velocity vector is computed in the inertial frame, it should
-be rotated to the instrument frame. Look at the previous step the
-routine that compute necessary rotation and rotate vectors.
+#. Since the velocity vector is computed in the inertial frame, it
+   should be rotated to the instrument frame. Look at the previous
+   step for the routine that computes the necessary rotation and
+   rotates vectors.
 
-Add calls to the routine(s), necessary variable declarations and output
-print statements to the program.
+#. Add calls to the routine(s), necessary variable declarations and
+   output print statements to the program.
 
 "Spacecraft Velocity" Solution Steps
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 All kernels required for computations in this step are already being
 loaded by the program, therefore, the meta-kernel does not need to be
 changed.
 
 The spacecraft velocity vector is the last three components of the state
-returned by spiceypy.spkezr. To compute velocity of CASSINI with respect
-to Phoebe in the J2000 inertial frame the spiceypy.spkezr arguments
+returned by :py:func:`spiceypy.spkezr `. To compute velocity of CASSINI with respect
+to Phoebe in the J2000 inertial frame the :py:func:`spiceypy.spkezr ` arguments
 should be set to 'CASSINI' (TARG), 'PHOEBE' (OBS), 'J2000' (REF) and
 'NONE' (ABCORR).
 
 The computed velocity vector has to be rotated from the J2000 frame to
-the instrument frame. The spiceypy.pxform routine used in the previous
+the instrument frame. The :py:func:`spiceypy.pxform ` routine used in the previous
 step can be used to compute the rotation matrix needed for that. In this
 case the frame name arguments should be set to 'J2000' (FROM) and
 'CASSINI_INMS' (TO).
 
 As in the previous step the difference vector should be then multiplied
-by this rotation matrix using the spiceypy.mxv routine. After applying
-the rotation, normalize the resultant vector using the spiceypy.vhat
+by this rotation matrix using the :py:func:`spiceypy.mxv ` routine. After applying
+the rotation, normalize the resultant vector using the :py:func:`spiceypy.vhat `
 routine.
 
 Putting it all together, we get:
@@ -1309,149 +1005,23 @@ output:
       SCVDIR(Z) =             0.870413
 
 Note that computing the spacecraft velocity in the instrument frame by a
-single call to spiceypy.spkezr by specifying 'CASSINI_INMS' in the
+single call to :py:func:`spiceypy.spkezr ` by specifying 'CASSINI_INMS' in the
 'ref' argument returns an incorrect result. Such computation will take
 into account the spacecraft angular velocity from the CK files, which
 should not be considered in this case.
 
 "Spacecraft Velocity" Code Program "scvel.py":
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-::
-
-      from __future__ import print_function
-      import spiceypy
-
-      def scvel():
-
-          mkfile = 'scvel.tm'
-          spiceypy.furnsh(mkfile)
-
-          utc =  '2004-06-11T19:32:00'
-          et = spiceypy.str2et(utc)
-
-          print('UTC       = {:s}'.format(utc))
-          print('ET        = {:20.6f}'.format(et))
-
-          scid = -82
-          sclk = '1465674964.105'
-          et = spiceypy.scs2e(scid, sclk)
-
-          print('SCLK      = {:s}'.format(sclk))
-          print('ET        = {:20.6f}'.format(et))
-
-          target = 'CASSINI'
-          frame  = 'ECLIPJ2000'
-          corrtn = 'NONE'
-          observ = 'SUN'
-
-          state, ltime = spiceypy.spkezr(target, et, frame,
-                                         corrtn, observ)
-
-          print(' X        = {:20.6f}'.format(state[0]))
-          print(' Y        = {:20.6f}'.format(state[1]))
-          print(' Z        = {:20.6f}'.format(state[2]))
-          print('VX        = {:20.6f}'.format(state[3]))
-          print('VY        = {:20.6f}'.format(state[4]))
-          print('VZ        = {:20.6f}'.format(state[5]))
-
-          target = 'SUN'
-          frame  = 'CASSINI_INMS'
-          corrtn = 'LT+S'
-          observ = 'CASSINI'
-
-          sundir, ltime = spiceypy.spkpos(target, et, frame,
-                                          corrtn, observ)
-          sundir = spiceypy.vhat(sundir)
-
-          print('SUNDIR(X) = {:20.6f}'.format(sundir[0]))
-          print('SUNDIR(Y) = {:20.6f}'.format(sundir[1]))
-          print('SUNDIR(Z) = {:20.6f}'.format(sundir[2]))
-
-          method = 'NEAR POINT: ELLIPSOID'
-          target = 'PHOEBE'
-          frame  = 'IAU_PHOEBE'
-          corrtn = 'NONE'
-          observ = 'CASSINI'
-
-          spoint, trgepc, srfvec = spiceypy.subpnt(method, target, et,
-                                                   frame, corrtn, observ)
-
-          srad, slon, slat = spiceypy.reclat(spoint)
-
-          fromfr = 'IAU_PHOEBE'
-          tofr   = 'CASSINI_INMS'
-
-          m2imat = spiceypy.pxform(fromfr, tofr, et)
-
-          sbpdir = spiceypy.mxv(m2imat, srfvec)
-          sbpdir = spiceypy.vhat(sbpdir)
-
-          print('LON       = {:20.6f}'.format(slon * spiceypy.dpr()))
-          print('LAT       = {:20.6f}'.format(slat * spiceypy.dpr()))
-          print('SBPDIR(X) = {:20.6f}'.format(sbpdir[0]))
-          print('SBPDIR(Y) = {:20.6f}'.format(sbpdir[1]))
-          print('SBPDIR(Z) = {:20.6f}'.format(sbpdir[2]))
-
-          target = 'CASSINI'
-          frame  = 'J2000'
-          corrtn = 'NONE'
-          observ = 'PHOEBE'
-
-          state, ltime = spiceypy.spkezr(target, et, frame,
-                                         corrtn, observ)
-          scvdir = state[3:6]
-
-          fromfr = 'J2000'
-          tofr   = 'CASSINI_INMS'
-          j2imat = spiceypy.pxform(fromfr, tofr, et)
-
-          scvdir = spiceypy.mxv(j2imat, scvdir)
-          scvdir = spiceypy.vhat(scvdir)
-
-          print('SCVDIR(X) = {:20.6f}'.format(scvdir[0]))
-          print('SCVDIR(Y) = {:20.6f}'.format(scvdir[1]))
-          print('SCVDIR(Z) = {:20.6f}'.format(scvdir[2]))
-
-          spiceypy.unload(mkfile)
-
-
-      if __name__ == '__main__':
-          scvel()
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 Meta-kernel file "scvel.tm":
 
-::
-
-      KPL/MK
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/scvel_make_mk.py
 
+Program "scvel.py":
 
-         File Name                   Description
-         --------------------------  ----------------------------------
-         naif0008.tls                Generic LSK.
-         cas00084.tsc                Cassini SCLK.
-         020514_SE_SAT105.bsp        Saturnian Satellite Ephemeris SPK.
-         030201AP_SK_SM546_T45.bsp   Cassini Spacecraft SPK.
-         981005_PLTEPH-DE405S.bsp    Planetary Ephemeris SPK.
-         sat128.bsp                  Saturnian Satellite Ephemeris SPK.
-         04135_04171pc_psiv2.bc      Cassini Spacecraft CK.
-         cas_v37.tf                  Cassini FK.
-         cpck05Mar2004.tpc           Cassini project PCK.
-
+.. py-editor::
+    :env: isenv
+    :src: scripts/insitu_sensing/scvel.py
 
-      \begindata
-         KERNELS_TO_LOAD = (
-                           'kernels/lsk/naif0008.tls'
-                           'kernels/sclk/cas00084.tsc'
-                           'kernels/spk/020514_SE_SAT105.bsp'
-                           'kernels/spk/030201AP_SK_SM546_T45.bsp'
-                           'kernels/spk/981005_PLTEPH-DE405S.bsp'
-                           'kernels/spk/sat128.bsp'
-                           'kernels/ck/04135_04171pc_psiv2.bc'
-                           'kernels/fk/cas_v37.tf'
-                           'kernels/pck/cpck05Mar2004.tpc'
-                           )
-      \begintext
diff --git a/docs/other_stuff.rst b/docs/other_stuff.rst
index 5f98f874..20ce8748 100644
--- a/docs/other_stuff.rst
+++ b/docs/other_stuff.rst
@@ -19,21 +19,13 @@ Overview
 This workbook contains lessons to demonstrate use of the less celebrated
 SpiceyPy routines.
 
-.. code-block:: text
-
-       1.   Kernel Management with the Kernel Subsystem
-
-       2.   The Kernel Pool
-
-       3.   Coordinate Conversions
-
-       4.   Advanced Time Manipulation Routines
-
-       5.   Error Handling
-
-       6.   Windows and Cells
-
-       7.   Utility and Constants Routines
+#. Kernel Management with the Kernel Subsystem
+#. The Kernel Pool
+#. Coordinate Conversions
+#. Advanced Time Manipulation Routines
+#. Error Handling
+#. Windows and Cells
+#. Utility and Constants Routines
 
 References
 ----------
@@ -57,10 +49,10 @@ this lesson:
 
 These tutorials are available from the NAIF ftp server at JPL:
 
-`https://naif.jpl.nasa.gov/naif/tutorials.html `_
+https://naif.jpl.nasa.gov/naif/tutorials.html
 
 Required Readings
-^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Required Readings `_ are also available on the NAIF website at:
@@ -81,7 +73,7 @@ installation tree.
       windows.req      The SPICE window data type
 
 The Permuted Index
-^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
 
 .. tip::
    The `Permuted Index `_ is also available on the NAIF website at:
@@ -96,22 +88,32 @@ discover which SpiceyPy functions perform functions of interest, as well
 as the names of the source files that contain these functions.
 
 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
+describes the str2et function's parameters, while the
 
-`https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html `_
+`str2et documentation `_
 
 describes extensively the str2et functionality.
 
@@ -131,7 +133,7 @@ The following kernels are used in examples provided in this lesson:
 These SPICE kernels are included in the lesson package available from
 the NAIF server at JPL:
 
-`https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/ `_
+https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/
 
 SpiceyPy Modules Used
 ---------------------
@@ -197,7 +199,7 @@ their corresponding CSPICE versions for detailed interface
 specifications.
 
 NAIF Documentation
-------------------------------
+------------------
 
 The technical complexity of the various SPICE subsystems mandates an
 extensive, user-friendly documentation set. The set differs somewhat
@@ -205,15 +207,10 @@ depending on your choice of development language but provides the same
 information with regards to SPICE operation. The sources for a user
 needing information concerning SPICE are:
 
-.. code-block:: text
-
-       --   Required Readings and Users Guides
-
-       --   Library Source Code Documentation
-
-       --   API Documentation
-
-       --   Tutorials
+- Required Readings and Users Guides
+- Library Source Code Documentation
+- API Documentation
+- Tutorials
 
 Required Reading and Users Guides
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -255,7 +252,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:
 
 ::
@@ -284,7 +281,7 @@ tools:
       version.ug
 
 Library Source Code Documentation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 All SPICELIB and CSPICE source files include usage and design
 information incorporated in a comment block known as the “header.”
@@ -292,65 +289,49 @@ information incorporated in a comment block known as the “header.”
 
 A header consists of several marked sections:
 
-.. code-block:: text
-
-       --   Procedure: Routine name and one line expansion of the routine's
-            name.
-
-       --   Abstract: A tersely worded explanation describing the routine.
-
-       --   Copyright: An identification of the copyright holder for the
-            routine.
-
-       --   Required_Reading: A list of SpiceyPy required reading documents
-            relating to the routine.
-
-       --   Brief_I/O: A table of arguments, identifying each as either
-            input, output, or both, with a very brief description of the
-            variable.
-
-       --   Detailed_Input & Detailed_Output: An elaboration of the
-            Brief_I/O section providing comprehensive information on
-            argument use.
-
-       --   Parameters: Description and declaration of any parameters
-            (constants) specific to the routine.
-
-       --   Exceptions: A list of error conditions the routine detects and
-            signals plus a discussion of any other exceptional conditions
-            the routine may encounter.
-
-       --   Files: A list of other files needed for the routine to operate.
-
-       --   Particulars: A discussion of the routine's function (if
-            needed). This section may also include information relating to
-            "how" and "why" the routine performs an operation and to
-            explain functionality of routines that operate by side effects.
-
-       --   Examples: Descriptions and code snippets concerning usage of
-            the routine.
-
-       --   Restrictions: Restrictions or warnings concerning use.
-
-       --   Literature_References: A list of sources required to understand
-            the algorithms or data used in the routine.
-
-       --   Author_and_Institution: The names and affiliations for authors
-            of the routine.
-
-       --   Version: A list of edits and the authors of those edits made to
-            the routine since initial delivery to the SpiceyPy system.
+- **Procedure**: Routine name and one line expansion of the routine's
+  name.
+- **Abstract**: A tersely worded explanation describing the routine.
+- **Copyright**: An identification of the copyright holder for the
+  routine.
+- **Required_Reading**: A list of SpiceyPy required reading documents
+  relating to the routine.
+- **Brief_I/O**: A table of arguments, identifying each as either
+  input, output, or both, with a very brief description of the
+  variable.
+- **Detailed_Input & Detailed_Output**: An elaboration of the
+  Brief_I/O section providing comprehensive information on
+  argument use.
+- **Parameters**: Description and declaration of any parameters
+  (constants) specific to the routine.
+- **Exceptions**: A list of error conditions the routine detects and
+  signals plus a discussion of any other exceptional conditions
+  the routine may encounter.
+- **Files**: A list of other files needed for the routine to operate.
+- **Particulars**: A discussion of the routine's function (if
+  needed). This section may also include information relating to
+  "how" and "why" the routine performs an operation and to
+  explain functionality of routines that operate by side effects.
+- **Examples**: Descriptions and code snippets concerning usage of
+  the routine.
+- **Restrictions**: Restrictions or warnings concerning use.
+- **Literature_References**: A list of sources required to understand
+  the algorithms or data used in the routine.
+- **Author_and_Institution**: The names and affiliations for authors
+  of the routine.
+- **Version**: A list of edits and the authors of those edits made to
+  the routine since initial delivery to the Spice system.
 
 The source code for SpiceyPy products is stored in 'src' sub-directory
 of the main SpiceyPy directory:
 
 API Documentation
-^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^
 
 The SpiceyPy package is documented in "readthedocs" website:
 
 
-`https://spiceypy.readthedocs.io/en/main/index.html `_
+https://spiceypy.readthedocs.io/en/main/index.html
 
 Each API documentation page is in large part copied from the
 "Abstract" and" Brief_I/O" sections of the corresponding CSPICE
@@ -361,22 +342,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:
+.. py-editor::
+    :env: other
 
-      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.
-
-             ...
-
-          :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
@@ -405,76 +375,72 @@ comment information to be ignored by the subsystem.
 
 Things to know:
 
-.. code-block:: text
-
-       1.   The \begindata tag marks the start of a data definition block.
-            The subsystem processes all text following this marker as SPICE
-            kernel data assignments until finding a \begintext marker.
+#. The ``\begindata`` tag marks the start of a data definition block.
+   The subsystem processes all text following this marker as SPICE
+   kernel data assignments until finding a ``\begintext`` marker.
 
-       2.   The kernel subsystem defaults to the \begintext mode until the
-            parser encounters a \begindata tag. Once in \begindata mode the
-            subsystem processes all text as variable assignments until the
-            next \begintext tag.
+#. The kernel subsystem defaults to the ``\begintext`` mode until the
+   parser encounters a ``\begindata`` tag. Once in ``\begindata`` mode the
+   subsystem processes all text as variable assignments until the
+   next ``\begintext`` tag.
 
-       3.   Enter the tags as the only text on a line, i.e.:
-
-
-         \begintext
+#. Enter the tags as the only text on a line, i.e.:
 
-            ... commentary information on the data assignments ...
+   .. code-block:: text
 
-         \begindata
+      \begintext
 
-            ... data assignments ...
+         ... commentary information on the data assignments ...
 
+      \begindata
 
-       4.   CSPICE delivery N0059 added to the CSPICE and Icy text kernel
-            parsers the functionality to read non native text kernels, i.e.
-            a Unix compiled library can read a MS Windows native text
-            kernel, a MS Windows compiled library can read a Unix native
-            text kernel. Mice acquires this capability from CSPICE.
+         ... data assignments ...
 
-       5.   With regards to the FORTRAN distribution, as of delivery N0057
-            the spiceypy.furnsh call includes a line terminator check,
-            signaling an error on any attempt to read non-native text
-            kernels.
+#. CSPICE delivery N0059 added to the CSPICE and Icy text kernel
+   parsers the functionality to read non native text kernels, i.e.
+   a Unix compiled library can read a MS Windows native text
+   kernel, a MS Windows compiled library can read a Unix native
+   text kernel. Mice acquires this capability from CSPICE.
 
-Text kernel format
+#. With regards to the FORTRAN distribution, as of delivery N0057
+   the :py:func:`spiceypy.furnsh ` call includes a line terminator check,
+   signaling an error on any attempt to read non-native text
+   kernels.
 
-Scalar assignments.
+**Text kernel format scalar assignments.**
 
 .. code-block:: text
 
-         VAR_NAME_DP  = 1.234
-         VAR_NAME_INT = 1234
-         VAR_NAME_STR = 'FORBIN'
+   VAR_NAME_DP  = 1.234
+   VAR_NAME_INT = 1234
+   VAR_NAME_STR = 'FORBIN'
 
 Please note the use of a single quote in string assignments.
 
-Vector assignments. Vectors must contain the same type data.
+**Vector assignments.** Vectors must contain the same type data.
 
 .. code-block:: text
 
-         VEC_NAME_DP  = ( 1.234   , 45.678  , 901234.5 )
-         VEC_NAME_INT = ( 1234    , 456     , 789      )
-         VEC_NAME_STR = ( 'FORBIN', 'FALKEN', 'ROBUR'  )
+   VEC_NAME_DP  = ( 1.234   , 45.678  , 901234.5 )
+   VEC_NAME_INT = ( 1234    , 456     , 789      )
+   VEC_NAME_STR = ( 'FORBIN', 'FALKEN', 'ROBUR'  )
 
-         also
+   also
 
-         VEC_NAME_DP  = ( 1.234,
-                         45.678,
-                         901234.5 )
+   VEC_NAME_DP  = ( 1.234,
+                   45.678,
+                   901234.5 )
 
-         VEC_NAME_STR = ( 'FORBIN',
-                          'FALKEN',
-                          'ROBUR' )
+   VEC_NAME_STR = ( 'FORBIN',
+                    'FALKEN',
+                    'ROBUR' )
 
-Time assignments.
+**Time assignments.**
 
 .. code-block:: text
 
-         TIME_VAL = @31-JAN-2003-12:34:56.798
-         TIME_VEC = ( @01-DEC-2004, @15-MAR-2004 )
+   TIME_VAL = @31-JAN-2003-12:34:56.798
+   TIME_VEC = ( @01-DEC-2004, @15-MAR-2004 )
 
 The at-sign character '@' indicates a time string. The pool subsystem
 converts the strings to double precision TDB (a numeric value). Please
@@ -513,140 +479,37 @@ 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
-
-      KPL/MK
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/kpool_make_mk.py
 
-      \begindata
-
-         KERNELS_TO_LOAD = ( 'kernels/spk/de405s.bsp',
-                             'kernels/pck/pck00008.tpc',
-                             'kernels/lsk/naif0008.tls' )
-
-      \begintext
 
 … or a more generic meta kernel using the PATH_VALUES/PATH_SYMBOLS
 functionality to declare path names as variables:
 
-.. code-block:: text
-
-      KPL/MK
-
-         Define the paths to the kernel directory. Use the PATH_SYMBOLS
-         as aliases to the paths.
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
-
-            File Name        Description
-            ---------------  ------------------------------
-            naif0008.tls     Generic LSK.
-            de405s.bsp       Planet Ephemeris SPK.
-            pck00008.tpc     Generic PCK.
-
-
-      \begindata
-
-         PATH_VALUES     = ( 'kernels/lsk',
-                             'kernels/spk',
-                             'kernels/pck' )
-
-         PATH_SYMBOLS    = ( 'LSK', 'SPK', 'PCK' )
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/kpool_generic_make_mk.py
 
-         KERNELS_TO_LOAD = ( '$LSK/naif0008.tls',
-                             '$SPK/de405s.bsp',
-                             '$PCK/pck00008.tpc' )
-
-      \begintext
 
 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) )
-
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/kpool.py
 
-          #
-          # 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
+**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.
+:py:func:`spiceypy.ktotal ` call.
 
-Then the spiceypy.kdata loop returns the name of each loaded kernel, the
+Then the :py:func:`spiceypy.kdata ` loop returns the name of each loaded kernel, the
 type of kernel (SPK, CK, TEXT, etc.) and the source of the kernel - the
 mechanism that loaded the kernel. The source either identifies a meta
 kernel, or contains an empty string. An empty source string indicates a
-direct load of the kernel with a spiceypy.furnsh call.
+direct load of the kernel with a :py:func:`spiceypy.furnsh ` call.
 
 .. code-block:: text
 
@@ -671,8 +534,10 @@ 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
-------------------------------
+-------------------------
 
 .. _task-statement-os-1:
 
@@ -695,70 +560,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
-
-      KPL/MK
-
-         Name the kernels to load. Use path symbols.
-
-         The names and contents of the kernels referenced by this
-         meta-kernel are as follows:
-
-            File Name        Description
-            ---------------  ------------------------------
-            naif0008.tls     Generic LSK.
-            de405s.bsp       Planet Ephemeris SPK.
-            pck00008.tpc     Generic PCK.
-
-
-      \begindata
-
-         PATH_VALUES     = ('kernels/spk',
-                            'kernels/pck',
-                            'kernels/lsk')
-
-         PATH_SYMBOLS    = ('SPK' , 'PCK' , 'LSK' )
-
-         KERNELS_TO_LOAD = ( '$SPK/de405s.bsp',
-                             '$PCK/pck00008.tpc',
-                             '$LSK/naif0008.tls')
-
-      \begintext
-
-      Ring model data.
-
-      \begindata
-
-         BODY699_RING1_NAME     = 'A Ring'
-         BODY699_RING1          = (122170.0 136780.0 0.1 0.1 0.5)
-
-         BODY699_RING1_1_NAME   = 'Encke Gap'
-         BODY699_RING1_1        = (133405.0 133730.0 0.0 0.0 0.0)
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/kervar_make_mk.py
 
-         BODY699_RING2_NAME     = 'Cassini Division'
-         BODY699_RING2          = (117580.0 122170.0 0.0 0.0 0.0)
-
-      \begintext
-
-      The kernel pool recognizes values preceded by '@' as time
-      values. When read, the kernel subsystem converts these
-      representations into double precision ephemeris time.
-
-      Caution: The kernel subsystem interprets the time strings
-      identified by '@' as TDB. The same string passed as input
-      to @STR2ET is processed as UTC.
-
-      The three expressions stored in the EXAMPLE_TIMES array represent
-      the same epoch.
-
-      \begindata
-
-         EXAMPLE_TIMES       = ( @APRIL-1-2004-12:34:56.789,
-                                 @4/1/2004-12:34:56.789,
-                                 @JD2453097.0242684
-                                )
-
-      \begintext
 
 The main references for pool routines are found in the help command, the
 CSPICE source files or the API documentation for the particular
@@ -769,150 +574,22 @@ 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():
-
-          #
-          # 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':
-
-                  #
-                  # 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 )
-
-          print( 'EXAMPLE_TIMES' )
-
-          for dvar in dvars:
-              print('  Time value:    {0:20.6f}'.format(dvar))
-
-          #
-          # Done. Unload the kernels.
-          #
-          spiceypy.kclear
-
-      if __name__ == '__main__':
-         kervar()
-
-Run the code example
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/kervar.py
+
+
+
+**Run the code example**
 
 The program runs and first reports the number of kernel pool variables
 matching the template, 6.
 
-The program then loops over the spiceypy.dtpool 6 times, reporting the
+The program then loops over :py:func:`spiceypy.dtpool ` 6 times, reporting the
 name of each pool variable, the number of data items assigned to that
-variable, and the variable type. Within the spiceypy.dtpool loop, a
+variable, and the variable type. Within the :py:func:`spiceypy.dtpool ` loop, a
 second loop outputs the contents of the data variable using
-spiceypy.gcpool or spiceypy.gdpool.
+:py:func:`spiceypy.gcpool ` or :py:func:`spiceypy.gdpool `.
 
 .. code-block:: text
 
@@ -974,15 +651,13 @@ decimal Julian day representation to the seconds past J2000 ET
 representation.
 
 Related Routines
-^^^^^^^^^^^^^^^^^
-
-.. code-block:: text
+^^^^^^^^^^^^^^^^
 
-       --   spiceypy.gipool retrieves integer values from the kernel
-            subsystem.
+- :py:func:`spiceypy.gipool ` retrieves integer values from the kernel
+  subsystem.
 
 Lesson 3: Coordinate Conversions
----------------------------------
+--------------------------------
 
 .. _task-statement-os-2:
 
@@ -1011,154 +686,19 @@ 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) )
-
-          #
-          # ...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
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/coord.py
+
+
+
+**Run the code example:**
+
+.. py-editor::
+    :env: other
+
+    coord("Feb 3 2002 TDB")
+
 
 Input “Feb 3 2002 TDB” to calculate the Moon's position. (the 'TDB' tag
 indicates a Barycentric Dynamical Time value).
@@ -1247,34 +787,22 @@ Geodetic. The cartographic lat/lon.
 .. _related-routines-1:
 
 Related Routines
-^^^^^^^^^^^^^^^^^
-
-.. code-block:: text
-
-       --   spiceypy.latrec, latitudinal to rectangular
-
-       --   spiceypy.latcyl, latitudinal to cylindrical
-
-       --   spiceypy.latsph, latitudinal to spherical
-
-       --   spiceypy.reccyl, rectangular to cylindrical
-
-       --   spiceypy.sphrec, spherical to rectangular
-
-       --   spiceypy.sphcyl, spherical to cylindrical
-
-       --   spiceypy.sphlat, spherical to latitudinal
-
-       --   spiceypy.cyllat, cylindrical to latitudinal
-
-       --   spiceypy.cylsph, cylindrical to spherical
-
-       --   spiceypy.cylrec, cylindrical to rectangular
-
-       --   spiceypy.georec, geodetic to rectangular
+^^^^^^^^^^^^^^^^
+
+- :py:func:`spiceypy.latrec `, latitudinal to rectangular
+- :py:func:`spiceypy.latcyl `, latitudinal to cylindrical
+- :py:func:`spiceypy.latsph `, latitudinal to spherical
+- :py:func:`spiceypy.reccyl `, rectangular to cylindrical
+- :py:func:`spiceypy.sphrec `, spherical to rectangular
+- :py:func:`spiceypy.sphcyl `, spherical to cylindrical
+- :py:func:`spiceypy.sphlat `, spherical to latitudinal
+- :py:func:`spiceypy.cyllat `, cylindrical to latitudinal
+- :py:func:`spiceypy.cylsph `, cylindrical to spherical
+- :py:func:`spiceypy.cylrec `, cylindrical to rectangular
+- :py:func:`spiceypy.georec `, geodetic to rectangular
 
 Lesson 4: Advanced Time Manipulation Routines
-----------------------------------------------
+---------------------------------------------
 
 .. _task-statement-os-3:
 
@@ -1302,121 +830,12 @@ 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()
-
-Run the code example
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/xtic.py
+
+
+**Run the code example**
 
 .. code-block:: text
 
@@ -1429,7 +848,7 @@ Run the code example
       Years between evaluations:           100.000000
 
 Lesson 5: Error Handling
-------------------------------
+------------------------
 
 .. _task-statement-os-4:
 
@@ -1460,100 +879,13 @@ 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
-
-
-      if __name__ == '__main__':
-         aderr()
-
-Run the code example
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/aderr.py
+
+
+
+**Run the code example**
 
 Now run the code with various inputs to observe behavior. Begin the run
 using known astronomical bodies, e.g. “Moon”, “Mars”, “Pluto barycenter”
@@ -1564,6 +896,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 +919,7 @@ evaluation time.
       =====================================================================
       ===========
 
-      Toolkit version: N0066
+      Toolkit version: N0067
 
       SPICE(SPKINSUFFDATA) --
 
@@ -1613,6 +946,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
@@ -1620,7 +959,7 @@ Try another look-up, this time for “Casper”
       =====================================================================
       ===========
 
-      Toolkit version: N0066
+      Toolkit version: N0067
 
       SPICE(IDCODENOTFOUND) --
 
@@ -1641,6 +980,12 @@ information on a body named 'Casper.'
 
 Another look-up, this time, “Venus”.
 
+.. py-editor::
+    :env: other
+
+    aderr('Venus')
+
+
 .. code-block:: text
 
       Target: Venus
@@ -1655,10 +1000,10 @@ can respond to error conditions (not system errors) in much the same
 fashion as languages with catch/throw instructions.
 
 Lesson 6: Windows, and Cells
-------------------------------
+----------------------------
 
 Programming task
-^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^
 
 Given the times of line-of-sight for a vehicle from a ground station and
 the times for an acceptable Sun-station-vehicle phase angle, write a
@@ -1710,147 +1055,13 @@ 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()
-
-Run the code example
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/win.py
+
+
+
+**Run the code example**
 
 The output window has the name \`sched' (schedule).
 
@@ -1888,39 +1099,26 @@ Finally, an analysis of the \`sched' data. The measure of an interval
 .. _related-routines-2:
 
 Related Routines
-^^^^^^^^^^^^^^^^^^
-
-.. code-block:: text
-
-       --   spiceypy.wncomd determines the compliment of a window with
-            respect to a defined interval.
-
-       --   spiceypy.wncond contracts a window's intervals.
-
-       --   spiceypy.wndifd : Calculate the difference between two windows;
-            i.e. every point existing in the first but not the second.
-
-       --   spiceypy.wnelmd returns TRUE or FALSE if a value exists in a
-            window.
-
-       --   spiceypy.wnexpd expands the size of the intervals in a window.
-
-       --   spiceypy.wnextd extracts a window's endpoints .
-
-       --   spiceypy.wnfild fills gaps between intervals in a window.
-
-       --   spiceypy.wnfltd filter/removes small intervals from a window.
-
-       --   spiceypy.wnincd determines if an interval exists within a
-            window.
-
-       --   spiceypy.wninsd inserts an interval into a window.
-
-       --   spiceypy.wnreld compares two windows. Comparison operations
-            available, equality '=', inequality '<>', subset '<=' and '>=',
-            proper subset '<' and '>'.
-
-       --   spiceypy.wnunid calculates the union of two windows.
+^^^^^^^^^^^^^^^^
+
+- :py:func:`spiceypy.wncomd ` determines the complement of a window with
+  respect to a defined interval.
+- :py:func:`spiceypy.wncond ` contracts a window's intervals.
+- :py:func:`spiceypy.wndifd `: Calculate the difference between two windows;
+  i.e. every point existing in the first but not the second.
+- :py:func:`spiceypy.wnelmd ` returns TRUE or FALSE if a value exists in a
+  window.
+- :py:func:`spiceypy.wnexpd ` expands the size of the intervals in a window.
+- :py:func:`spiceypy.wnextd ` extracts a window's endpoints.
+- :py:func:`spiceypy.wnfild ` fills gaps between intervals in a window.
+- :py:func:`spiceypy.wnfltd ` filter/removes small intervals from a window.
+- :py:func:`spiceypy.wnincd ` determines if an interval exists within a
+  window.
+- :py:func:`spiceypy.wninsd ` inserts an interval into a window.
+- :py:func:`spiceypy.wnreld ` compares two windows. Comparison operations
+  available, equality '=', inequality '<>', subset '<=' and '>=',
+  proper subset '<' and '>'.
+- :py:func:`spiceypy.wnunid ` calculates the union of two windows.
 
 Lesson 7: Utility and Constants Routines
 ----------------------------------------
@@ -1950,108 +1148,12 @@ 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':
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/units.py
 
-              #
-              # ... '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'
-
-          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
+**Run the code example**
 
 Run a few conversions through the application to ensure it works. The
 intro banner gives us the Toolkit version against which the application
@@ -2059,7 +1161,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
@@ -2071,9 +1173,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
@@ -2092,96 +1194,11 @@ calculate some rudimentary values.
 Code Solution
 ^^^^^^^^^^^^^
 
-.. code-block:: python
-
-      from __future__ import print_function
-
-      #
-      # Import the CSPICE-Python interface.
-      #
-      import spiceypy
-
-      def xconst():
-
-          #
-          # All the function have the same calling sequence:
-          #
-          #    VALUE = function_name()
-          #
-          #    some_procedure( function_name() )
-          #
-          # First a simple example using the seconds per day
-          # constant...
-          #
-          print( 'Number of (S)econds (P)er (D)ay         : '
-                 '{0:19.12f}'.format(spiceypy.spd() ))
-
-          #
-          # ...then show the value of degrees per radian, 180/Pi...
-          #
-          print( 'Number of (D)egrees (P)er (R)adian      : '
-                 '{0:19.16f}'.format(spiceypy.dpr() ))
-
-          #
-          # ...and the inverse, radians per degree, Pi/180.
-          # It is obvious spiceypy.dpr() equals 1.d/spiceypy.rpd(), or
-          # more simply spiceypy.dpr() * spiceypy.rpd() equals 1
-          #
-          print( 'Number of (R)adians (P)er (D)egree      : '
-                 '{0:19.16f}'.format(spiceypy.rpd() ))
-
-          #
-          # What's the value for the astrophysicist's favorite
-          # physical constant (in a vacuum)?
-          #
-          print( 'Speed of light in KM per second         : '
-                 '{0:19.12f}'.format(spiceypy.clight() ))
-
-          #
-          # How long (in Julian days) from the J2000 epoch to the
-          # J2100 epoch?
-          #
-          print( 'Number of days between epochs J2000')
-          print( '  and J2100                             : '
-                 '{0:19.12f}'.format(  spiceypy.j2100()
-                                     - spiceypy.j2000() ))
-
-          #
-          # Redo the calculation returning seconds...
-          #
-          print( 'Number of seconds between epochs' )
-          print( '  J2000 and J2100                       : '
-                 '{0:19.5f}'.format(spiceypy.spd() *          \
-                 (spiceypy.j2100() - spiceypy.j2000() ) ))
-
-
-          #
-          # ...then tropical years.
-          #
-          val =(spiceypy.spd()/spiceypy.tyear()    ) *        \
-               (spiceypy.j2100()- spiceypy.j2000() )
-          print( 'Number of tropical years between' )
-          print( '  epochs J2000 and J2100                : '
-                 '{0:19.12f}'.format(val))
-
-
-          #
-          # Finally, how can I convert a radian value to degrees.
-          #
-          print( 'Number of degrees in Pi/2 radians of arc: '
-                 '{0:19.16f}'.format(  spiceypy.halfpi()
-                                     * spiceypy.dpr()      ))
-
-          #
-          # and degrees to radians.
-          #
-          print( 'Number of radians in 250 degrees of arc : '
-                 '{0:19.16f}'.format(250. * spiceypy.rpd() ))
-
-      if __name__ == '__main__':
-         xconst()
-
-Run the code example
+.. py-editor::
+    :env: other
+    :src: scripts/other_stuff/xconst.py
+
+**Run the code example**
 
 ::
 
@@ -2201,22 +1218,14 @@ Run the code example
 .. _related-routines-3:
 
 Related Routines
-^^^^^^^^^^^^^^^^^^
-
-.. code-block:: text
-
-       --   spiceypy.b1900 : Julian Date of the epoch Besselian Date 1900.0
-
-       --   spiceypy.b1950 : Julian date of the epoch Besselian Date 1950.0
-
-       --   spiceypy.j1900 : Julian date of 1900 JAN 0.5 this corresponds
-            to calendar date 1899 DEC 31 12:00:00
-
-       --   spiceypy.j1950 : Julian date of 1950 JAN 1.0 this corresponds
-            to calendar date 1950 JAN 01 00:00:00
-
-       --   spiceypy.twopi : double precision value of 2 * Pi
-
-       --   spiceypy.pi : double precision value of Pi
-
-       --   spiceypy.jyear : seconds per Julian year (365.25 Julian days)
+^^^^^^^^^^^^^^^^
+
+- :py:func:`spiceypy.b1900 `: Julian Date of the epoch Besselian Date 1900.0
+- :py:func:`spiceypy.b1950 `: Julian date of the epoch Besselian Date 1950.0
+- :py:func:`spiceypy.j1900 `: Julian date of 1900 JAN 0.5 this corresponds
+  to calendar date 1899 DEC 31 12:00:00
+- :py:func:`spiceypy.j1950 `: Julian date of 1950 JAN 1.0 this corresponds
+  to calendar date 1950 JAN 01 00:00:00
+- :py:func:`spiceypy.twopi `: double precision value of 2 * Pi
+- :py:func:`spiceypy.pi `: double precision value of Pi
+- :py:func:`spiceypy.jyear `: seconds per Julian year (365.25 Julian days)
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_binary_pck.json b/docs/pyscript_binary_pck.json new file mode 100644 index 00000000..629763e9 --- /dev/null +++ b/docs/pyscript_binary_pck.json @@ -0,0 +1,16 @@ +{ + "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/de414_2000_2020.bsp": "kernels/spk/de414_2000_2020.bsp", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/moon_060721.tf": "kernels/fk/moon_060721.tf", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/pck00008.tpc": "kernels/pck/pck00008.tpc", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/moon_pa_de403_1950-2198.bpc": "kernels/pck/moon_pa_de403_1950-2198.bpc", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/earthstns_itrf93_050714.bsp": "kernels/spk/earthstns_itrf93_050714.bsp", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/earth_topo_050714.tf": "kernels/fk/earth_topo_050714.tf", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/earth_000101_070725_070503.bpc": "kernels/pck/earth_000101_070725_070503.bpc" + } +} diff --git a/docs/pyscript_editor.py b/docs/pyscript_editor.py new file mode 100644 index 00000000..de148fac --- /dev/null +++ b/docs/pyscript_editor.py @@ -0,0 +1,275 @@ +""" +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. + +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 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
+-------------------
+Basic (uses global defaults from conf.py)::
+
+    .. py-editor::
+
+        import numpy as np
+        print(np.__version__)
+
+Override any option per block::
+
+    .. py-editor::
+        :env: myenv
+        :config: my_pyscript.json
+
+        print("hello")
+
+Directive options
+-----------------
+:env:    PyScript environment name (``env=`` attribute on ``
+"""
+
+
+def _head_html(mini_coi: str, version: str, hide_gutters: bool, hide_env_label: bool) -> str:
+    parts = []
+    if mini_coi:
+        parts.append(f'')
+    parts.append(
+        f''
+    )
+    parts.append(
+        f''
+    )
+    if hide_env_label:
+        parts.append(_HIDE_ENV_LABEL_CSS)
+    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,
+        "src": directives.unchanged,
+        "target": directives.unchanged,
+        "setup": directives.flag,
+    }
+
+    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
+        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_src = self.options.get("src", None)
+        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, hide_env_label)))
+            injected.add(env.docname)
+            setattr(env, _HEAD_KEY, injected)
+
+        # ---- 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 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)
+        else:
+            config_part = ""
+
+        # ---- build the editor HTML ----
+        if ed_src:
+            abs_src = os.path.join(env.srcdir, ed_src)
+            try:
+                with open(abs_src, encoding="utf-8") as fh:
+                    code = fh.read()
+            except OSError as exc:
+                raise self.error(f":src: could not read file {abs_src!r}: {exc}") from exc
+            env.note_dependency(abs_src)
+        else:
+            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' + 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_config_value("pyscript_hide_env_label", True, "html") + + app.add_directive("py-editor", PyEditorDirective) + + return { + "version": "0.1.0", + "parallel_read_safe": True, + "parallel_write_safe": True, + } diff --git a/docs/pyscript_event_finding.json b/docs/pyscript_event_finding.json new file mode 100644 index 00000000..e49460f9 --- /dev/null +++ b/docs/pyscript_event_finding.json @@ -0,0 +1,16 @@ +{ + "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/de405xs.bsp": "kernels/spk/de405xs.bsp", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/earthstns_itrf93_050714.bsp": "kernels/spk/earthstns_itrf93_050714.bsp", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/earth_topo_050714.tf": "kernels/fk/earth_topo_050714.tf", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/earth_000101_060525_060303.bpc": "kernels/pck/earth_000101_060525_060303.bpc", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/ORMM__040501000000_00076XS.BSP": "kernels/spk/ORMM__040501000000_00076XS.BSP", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/pck00008.tpc": "kernels/pck/pck00008.tpc", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/mars_lowres.bds": "kernels/dsk/mars_lowres.bds" + } +} diff --git a/docs/pyscript_insitu_sensing.json b/docs/pyscript_insitu_sensing.json new file mode 100644 index 00000000..3814f1d3 --- /dev/null +++ b/docs/pyscript_insitu_sensing.json @@ -0,0 +1,20 @@ +{ + "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", + "https://raw.githubusercontent.com/AndrewAnnex/spiceypylessonkernels/refs/heads/main/sat128.bsp": "kernels/spk/sat128.bsp" + } +} 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/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 e860e6b9..89bc39b9 100644 --- a/docs/remote_sensing.rst +++ b/docs/remote_sensing.rst @@ -26,11 +26,11 @@ often contain comprehensive descriptions of the frames, instrument FOVs, etc. Since both the FK and IK are text kernels, the information provided in them can be viewed using any text editor, while the meta information provided in binary kernels—SPKs and CKs—can be viewed using -"commnt" or" spacit" utility programs located in "cspice/exe" of +"commnt" or "spacit" utility programs located in "cspice/exe" of Toolkit installation tree. Tutorials -^^^^^^^^^^ +^^^^^^^^^ The following SPICE tutorials serve as references for the discussions in this lesson: @@ -50,12 +50,10 @@ this lesson: These tutorials are available from the NAIF ftp server at JPL: -.. code-block:: text - - https://naif.jpl.nasa.gov/naif/tutorials.html +https://naif.jpl.nasa.gov/naif/tutorials.html Required Readings -^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^ .. tip:: The `Required Readings `_ are also available on the NAIF website at: @@ -79,7 +77,7 @@ installation tree. time.req Time conversion The Permuted Index -^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^ .. tip:: The `Permuted Index `_ is also available on the NAIF website at: @@ -95,25 +93,33 @@ discover which SpiceyPy functions perform functions of interest, as well as the names of the source files that contain these functions. SpiceyPy API Documentation -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^ A SpiceyPy function's parameters specification is available using the built-in Python help system. For example, the Python help function -.. code-block:: python +.. py-editor:: + :env: rsenv + :config: pyscript_remote_sensing.json + :setup: + + import spiceypy + +.. py-editor:: + :env: rsenv + :config: pyscript_remote_sensing.json import spiceypy + help(spiceypy.str2et) -describes of the str2et function's parameters, while the document - -.. code-block:: text +describes the ``str2et`` function's parameters, while the document - https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html +https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/str2et_c.html -describes extensively the str2et functionality. +describes extensively the ``str2et`` functionality. Kernels Used ------------ @@ -138,9 +144,7 @@ The following kernels are used in examples provided in this lesson: These SPICE kernels are included in the lesson package available from the NAIF server at JPL: -.. code-block:: text - - ftp://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/ +ftp://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/Lessons/ In addition to these kernels, the extra credit exercises require the following kernels: @@ -153,9 +157,7 @@ following kernels: These SPICE kernels are available from the NAIF server at JPL: -.. code-block:: text - - https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/satellites/ +https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/satellites/ SpiceyPy Modules Used --------------------- @@ -227,7 +229,7 @@ their corresponding CSPICE versions for detailed interface specifications. Time Conversion (convtm) ------------------------------- +------------------------ Task Statement ^^^^^^^^^^^^^^ @@ -235,13 +237,11 @@ Task Statement Write a program that prompts the user for an input UTC time string, converts it to the following time systems and output formats: -.. code-block:: text +#. Ephemeris Time (ET) in seconds past J2000 - 1. Ephemeris Time (ET) in seconds past J2000 +#. Calendar Ephemeris Time - 2. Calendar Ephemeris Time - - 3. Spacecraft Clock Time +#. Spacecraft Clock Time and displays the results. Use the program to convert “2004 jun 11 19:32:00” UTC into these alternate systems. @@ -259,127 +259,50 @@ Approach The solution to the problem can be broken down into a series of simple steps: -.. code-block:: text - - -- Decide which SPICE kernels are necessary. Prepare a meta-kernel - listing the kernels and load it into the program. +- Decide which SPICE kernels are necessary. Prepare a meta-kernel + listing the kernels and load it into the program. - -- Prompt the user for an input UTC time string. +- Prompt the user for an input UTC time string. - -- Convert the input time string into ephemeris time expressed as - seconds past J2000 TDB. Display the result. +- Convert the input time string into ephemeris time expressed as + seconds past J2000 TDB. Display the result. - -- Convert ephemeris time into a calendar format. Display the - result. +- Convert ephemeris time into a calendar format. Display the + result. - -- Convert ephemeris time into a spacecraft clock string. Display - the result. +- Convert ephemeris time into a spacecraft clock string. Display + the result. You may find it useful to consult the permuted index, the headers of various source modules, and the -"Time Required Reading" (time.req) and" SCLK Required Reading" +"Time Required Reading" (time.req) and "SCLK Required Reading" (sclk.req) documents. When completing the "calendar format" step above, consider using one -of two possible methods: spiceypy.etcal or spiceypy.timout. +of two possible methods: :py:func:`spiceypy.etcal ` or :py:func:`spiceypy.timout `. Solution ^^^^^^^^ -Solution Meta-Kernel +**Solution Meta-Kernel** The meta-kernel we created for the solution to this exercise is named 'convtm.tm'. Its contents follow: -.. code-block:: text - - KPL/MK - - This is the meta-kernel used in the solution of the "Time - Conversion" task in the Remote Sensing Hands On Lesson. - - The names and contents of the kernels referenced by this - meta-kernel are as follows: +.. py-editor:: + :env: rsenv + :config: pyscript_remote_sensing.json + :src: scripts/remote_sensing/convtm_make_mk.py - File name Contents - -------------------------- ----------------------------- - naif0008.tls Generic LSK - cas00084.tsc Cassini SCLK - - - \begindata - KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls', - 'kernels/sclk/cas00084.tsc' ) - \begintext - -Solution Source Code +**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 ) +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/convtm.py - 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() - -Solution Sample Output +**Solution Sample Output** Execute the program: @@ -393,7 +316,7 @@ Execute the program: Spacecraft Clock Time: 1/1465674964.105 Extra Credit -^^^^^^^^^^^^^ +^^^^^^^^^^^^ In this "extra credit" section you will be presented with more complex tasks, aimed at improving your understanding of time @@ -405,115 +328,86 @@ unlike the regular tasks, no approach or solution source code is provided. In the next section, you will find the numeric solutions (when applicable) and answers to the questions asked in these tasks. -Task statements and questions +**Task statements and questions** -.. code-block:: text +#. Extend your program to convert the input UTC time string to TDB + Julian Date. Convert "2004 jun 11 19:32:00" UTC. - 1. Extend your program to convert the input UTC time string to TDB - Julian Date. Convert "2004 jun 11 19:32:00" UTC. +#. Remove the LSK from the original meta-kernel and run your + program again, using the same inputs as before. Has anything + changed? Why? - 2. Remove the LSK from the original meta-kernel and run your - program again, using the same inputs as before. Has anything - changed? Why? +#. Remove the SCLK from the original meta-kernel and run your + program again, using the same inputs as before. Has anything + changed? Why? - 3. Remove the SCLK from the original meta-kernel and run your - program again, using the same inputs as before. Has anything - changed? Why? +#. Modify your program to perform conversion of UTC or ephemeris + time, to a spacecraft clock string using the NAIF ID for the + CASSINI ISS NAC camera. Convert "2004 jun 11 19:32:00" UTC. - 4. Modify your program to perform conversion of UTC or ephemeris - time, to a spacecraft clock string using the NAIF ID for the - CASSINI ISS NAC camera. Convert "2004 jun 11 19:32:00" UTC. +#. Find the earliest UTC time that can be converted to CASSINI + spacecraft clock. - 5. Find the earliest UTC time that can be converted to CASSINI - spacecraft clock. +#. Extend your program to convert the spacecraft clock time + obtained in the regular task back to UTC Time and present it in + ISO calendar date format, with a resolution of milliseconds. - 6. Extend your program to convert the spacecraft clock time - obtained in the regular task back to UTC Time and present it in - ISO calendar date format, with a resolution of milliseconds. +#. Examine the contents of the generic LSK and the CASSINI SCLK + kernels. Can you understand and explain what you see? - 7. Examine the contents of the generic LSK and the CASSINI SCLK - kernels. Can you understand and explain what you see? +**Solutions and answers** -Solutions and answers +#. Two methods exist in order to convert ephemeris time to Julian + Date: :py:func:`spiceypy.unitim ` and :py:func:`spiceypy.timout `. The difference + between them is the type of output produced by each method. + :py:func:`spiceypy.unitim ` returns the double precision value of an input + epoch, while :py:func:`spiceypy.timout ` returns the string representation + of the ephemeris time in Julian Date format (when picture input + is set to ``'JULIAND.######### ::TDB'``). Refer to the function + header for further details. The solution for the requested + input UTC string is: ``Julian Date TDB: 2453168.3146318`` -.. code-block:: text +#. When running the original program without the LSK kernel, an + error is produced: + + .. code-block:: text - 1. Two methods exist in order to convert ephemeris time to Julian - Date: spiceypy.unitim and spiceypy.timout. The difference - between them is the type of output produced by each method. - spiceypy.unitim returns the double precision value of an input - epoch, while spiceypy.timout returns the string representation - of the ephemeris time in Julian Date format (when picture input - is set to 'JULIAND.######### ::TDB'). Refer to the function - header for further details. The solution for the requested - input UTC string is: - - Julian Date TDB: 2453168.3146318 - - 2. When running the original program without the LSK kernel, an - error is produced: - - Traceback (most recent call last): - File "convtm.py", line 67, in - convtm() - File "convtm.py", line 30, in convtm - et = spiceypy.str2et( utctim ) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 76, in with_errcheck - check_for_spice_error(f) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 59, in check_for_spice_error - raise stypes.SpiceyError(msg) spiceypy.utils.support_types.SpiceyError: - ===================================================================== - =========== + ================================================================================ - Toolkit version: N0066 + Toolkit version: N0067 SPICE(NOLEAPSECONDS) -- - The variable that points to the leapseconds (DELTET/DELTA_AT) could n - ot be located in the kernel pool. It is likely that the leapseconds - kernel has not been loaded via the routine FURNSH. + The variable that points to the leapseconds (DELTET/DELTA_AT) could not be located in the kernel pool. It is likely that the leapseconds kernel has not been loaded via the routine FURNSH. str2et_c --> STR2ET --> TTRANS - ===================================================================== - =========== - - This error is triggered by spiceypy.str2et because the variable - that points to the leapseconds is not present in the kernel - pool and therefore the program lacks data required to perform - the requested UTC to ephemeris time conversion. - - By default, SPICE will report, as a minimum, a short - descriptive message and a expanded form of this short message - where more details about the error are provided. If this error - message is not sufficient for you to understand what has - happened, you could go to the "Exceptions" section in the - SPICELIB or CSPICE headers of the function that has triggered - the error and find out more information about the possible - causes. - - 3. When running the original program without the SCLK kernel, an - error is produced: - - Traceback (most recent call last): - File "convtm.py", line 67, in - convtm() - File "convtm.py", line 58, in convtm - sclkst = spiceypy.sce2s( SCLKID, et ) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 76, in with_errcheck - check_for_spice_error(f) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 59, in check_for_spice_error - raise stypes.SpiceyError(msg) + ================================================================================ + + This error is triggered by :py:func:`spiceypy.str2et ` because the variable + that points to the leapseconds is not present in the kernel + pool and therefore the program lacks data required to perform + the requested UTC to ephemeris time conversion. + + By default, SPICE will report, as a minimum, a short + descriptive message and an expanded form of this short message + where more details about the error are provided. If this error + message is not sufficient for you to understand what has + happened, you could go to the "Exceptions" section in the + SPICELIB or CSPICE headers of the function that has triggered + the error and find out more information about the possible + causes. + +#. When running the original program without the SCLK kernel, an + error is produced: + + .. code-block:: text + spiceypy.utils.support_types.SpiceyError: - ===================================================================== - =========== + ================================================================================ - Toolkit version: N0066 + Toolkit version: N0067 SPICE(KERNELVARNOTFOUND) -- The Variable Was not Found in the Kernel Pool. @@ -521,54 +415,54 @@ Solutions and answers sce2s_c --> SCE2S --> SCE2T --> SCTYPE --> SCLI01 - ===================================================================== - =========== - - This error is triggered by spiceypy.sce2s. In this case the - error message may not give you enough information to understand - what has actually happened. Nevertheless, the expanded form of - this short message clearly indicates that the SCLK kernel for - the spacecraft ID -82 has not been loaded. - - The UTC string to ephemeris time conversion and the conversion - of ephemeris time into a calendar format worked normally as - these conversions only require the LSK kernel to be loaded. - - 4. The first thing you need to do is to find out what the NAIF ID - is for the CASSINI ISS NAC camera. In order to do so, examine - the ISS instrument kernel listed above and look for the "NAIF - ID Code to Name Mapping" and there, for the NAIF ID given to - CASSINI_ISS_NAC (which is -82360). Then replace in your code - the SCLK ID -82 with -82360. After executing the program using - the original meta-kernel, you will be getting the same error as - in the previous task. Despite the error being exactly the same, - this case is different. Generally, spacecraft clocks are - associated with the spacecraft ID and not with its payload, - sensors or structures IDs. Therefore, in order to do - conversions from/to spacecraft clock for payload, sensors or - spacecraft structures, the spacecraft ID must be used. - - Note that this does not need to be true for all missions or - payloads, as SPICE does not restrict the SCLKs to spacecraft - IDs only. Please refer to your mission's SCLK kernels for - particulars. - - 5. Use spiceypy.sct2e with the encoding of the Cassini spacecraft - clock time set to 0.0 ticks and convert the resulting ephemeris - time to UTC using either spiceypy.timout or spiceypy.et2utc. - The solution for the requested SCLK string is: - - Earliest UTC convertible to SCLK: 1980-01-01T00:00:00.000 - - 6. Use spiceypy.scs2e with the SCLK string obtained in the - computations performed in the regular tasks and convert the - resulting ephemeris time to UTC using either spiceypy.et2utc, - with 'ISOC' format and 3 digits precision, or using - spiceypy.timout using the time picture 'YYYY-MM-DDTHR:MN:SC.### - ::RND'. The solution of the requested conversion is: - - Spacecraft Clock Time: 1/1465674964.105 - UTC time from spacecraft clock: 2004-06-11T19:31:59.999 + ================================================================================ + + This error is triggered by :py:func:`spiceypy.sce2s `. In this case the + error message may not give you enough information to understand + what has actually happened. Nevertheless, the expanded form of + this short message clearly indicates that the SCLK kernel for + the spacecraft ID -82 has not been loaded. + + The UTC string to ephemeris time conversion and the conversion + of ephemeris time into a calendar format worked normally as + these conversions only require the LSK kernel to be loaded. + +#. The first thing you need to do is to find out what the NAIF ID + is for the CASSINI ISS NAC camera. In order to do so, examine + the ISS instrument kernel listed above and look for the "NAIF + ID Code to Name Mapping" and there, for the NAIF ID given to + CASSINI_ISS_NAC (which is -82360). Then replace in your code + the SCLK ID -82 with -82360. After executing the program using + the original meta-kernel, you will be getting the same error as + in the previous task. Despite the error being exactly the same, + this case is different. Generally, spacecraft clocks are + associated with the spacecraft ID and not with its payload, + sensors or structures IDs. Therefore, in order to do + conversions from/to spacecraft clock for payload, sensors or + spacecraft structures, the spacecraft ID must be used. + + Note that this does not need to be true for all missions or + payloads, as SPICE does not restrict the SCLKs to spacecraft + IDs only. Please refer to your mission's SCLK kernels for + particulars. + +#. Use :py:func:`spiceypy.sct2e ` with the encoding of the Cassini spacecraft + clock time set to 0.0 ticks and convert the resulting ephemeris + time to UTC using either :py:func:`spiceypy.timout ` or :py:func:`spiceypy.et2utc `. + The solution for the requested SCLK string is: + ``Earliest UTC convertible to SCLK: 1980-01-01T00:00:00.000`` + +#. Use :py:func:`spiceypy.scs2e ` with the SCLK string obtained in the + computations performed in the regular tasks and convert the + resulting ephemeris time to UTC using either :py:func:`spiceypy.et2utc `, + with ``'ISOC'`` format and 3 digits precision, or using + :py:func:`spiceypy.timout ` using the time picture ``'YYYY-MM-DDTHR:MN:SC.### ::RND'``. + The solution of the requested conversion is: + + .. code-block:: text + + Spacecraft Clock Time: 1/1465674964.105 + UTC time from spacecraft clock: 2004-06-11T19:31:59.999 Obtaining Target States and Positions (getsta) ---------------------------------------------- @@ -581,24 +475,22 @@ Task Statement Write a program that prompts the user for an input UTC time string, computes the following quantities at that epoch: -.. code-block:: text - - 1. The apparent state of Phoebe as seen from CASSINI in the J2000 - frame, in kilometers and kilometers/second. This vector itself - is not of any particular interest, but it is a useful - intermediate quantity in some geometry calculations. +#. The apparent state of Phoebe as seen from CASSINI in the J2000 + frame, in kilometers and kilometers/second. This vector itself + is not of any particular interest, but it is a useful + intermediate quantity in some geometry calculations. - 2. The apparent position of the Earth as seen from CASSINI in the - J2000 frame, in kilometers. +#. The apparent position of the Earth as seen from CASSINI in the + J2000 frame, in kilometers. - 3. The one-way light time between CASSINI and the apparent - position of Earth, in seconds. +#. The one-way light time between CASSINI and the apparent + position of Earth, in seconds. - 4. The apparent position of the Sun as seen from Phoebe in the - J2000 frame (J2000), in kilometers. +#. The apparent position of the Sun as seen from Phoebe in the + J2000 frame (J2000), in kilometers. - 5. The actual (geometric) distance between the Sun and Phoebe, in - astronomical units. +#. The actual (geometric) distance between the Sun and Phoebe, in + astronomical units. and displays the results. Use the program to compute these quantities at “2004 jun 11 19:32:00” UTC. @@ -608,8 +500,8 @@ and displays the results. Use the program to compute these quantities at Learning Goals ^^^^^^^^^^^^^^ -Understand the anatomy of an spiceypy.spkezr call. Discover the -difference between spiceypy.spkezr and spiceypy.spkpos. Familiarity with +Understand the anatomy of an :py:func:`spiceypy.spkezr ` call. Discover the +difference between :py:func:`spiceypy.spkezr ` and :py:func:`spiceypy.spkpos `. Familiarity with the Toolkit utility "brief". Exposure to unit conversion with SpiceyPy. @@ -621,34 +513,32 @@ Approach The solution to the problem can be broken down into a series of simple steps: -.. code-block:: text - - -- Decide which SPICE kernels are necessary. Prepare a meta-kernel - listing the kernels and load it into the program. +- Decide which SPICE kernels are necessary. Prepare a meta-kernel + listing the kernels and load it into the program. - -- Prompt the user for an input time string. +- Prompt the user for an input time string. - -- Convert the input time string into ephemeris time expressed as - seconds past J2000 TDB. +- Convert the input time string into ephemeris time expressed as + seconds past J2000 TDB. - -- Compute the state of Phoebe relative to CASSINI in the J2000 - reference frame, corrected for aberrations. +- Compute the state of Phoebe relative to CASSINI in the J2000 + reference frame, corrected for aberrations. - -- Compute the position of Earth relative to CASSINI in the J2000 - reference frame, corrected for aberrations. (The function in - the library that computes this also returns the one-way light - time between CASSINI and Earth.) +- Compute the position of Earth relative to CASSINI in the J2000 + reference frame, corrected for aberrations. (The function in + the library that computes this also returns the one-way light + time between CASSINI and Earth.) - -- Compute the position of the Sun relative to Phoebe in the J2000 - reference frame, corrected for aberrations. +- Compute the position of the Sun relative to Phoebe in the J2000 + reference frame, corrected for aberrations. - -- Compute the position of the Sun relative to Phoebe without - correcting for aberration. +- Compute the position of the Sun relative to Phoebe without + correcting for aberration. - Compute the length of this vector. This provides the desired - distance in kilometers. + Compute the length of this vector. This provides the desired + distance in kilometers. - -- Convert the distance in kilometers into AU. +- Convert the distance in kilometers into AU. You may find it useful to consult the permuted index, the headers of various source modules, and the "SPK Required Reading" (spk.req) @@ -657,7 +547,7 @@ document. When deciding which SPK files to load, the Toolkit utility "brief" may be of some use. -"brief" is located in the" cspice/exe"directory for C toolkits. +"brief" is located in the "cspice/exe" directory for C toolkits. Consult its user's guide available in "cspice/doc/brief.ug" for details. @@ -666,170 +556,24 @@ details. Solution ^^^^^^^^ -Solution Meta-Kernel +**Solution Meta-Kernel** The meta-kernel we created for the solution to this exercise is named 'getsta.tm'. Its contents follow: -.. code-block:: text - - 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. +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/getsta_make_mk.py - 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 - - - \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 - -Solution Source Code +**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() - -Solution Sample Output +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/getsta.py + +**Solution Sample Output** Execute the program: @@ -864,12 +608,12 @@ Execute the program: .. _extra-credit-rs-1: Extra Credit -^^^^^^^^^^^^^ +^^^^^^^^^^^^ In this "extra credit" section you will be presented with more complex tasks, aimed at improving your understanding of state computations, particularly the application of the different light time -and stellar aberration corrections available in the spiceypy.spkezr +and stellar aberration corrections available in the :py:func:`spiceypy.spkezr ` function, and some common errors that may happen when computing these states. @@ -878,79 +622,63 @@ unlike the regular tasks, no approach or solution source code is provided. In the next section, you will find the numeric solutions (when applicable) and answers to the questions asked in these tasks. -Task statements and questions +**Task statements and questions** -.. code-block:: text +#. Remove the Solar System ephemerides SPK from the original + meta-kernel and run your program again, using the same inputs + as before. Has anything changed? Why? - 1. Remove the Solar System ephemerides SPK from the original - meta-kernel and run your program again, using the same inputs - as before. Has anything changed? Why? +#. Extend your program to compute the geometric position of + Jupiter as seen from Saturn in the J2000 frame (J2000), in + kilometers. - 2. Extend your program to compute the geometric position of - Jupiter as seen from Saturn in the J2000 frame (J2000), in - kilometers. +#. Extend, or modify, your program to compute the position of the + Sun as seen from Saturn in the J2000 frame (J2000), in + kilometers, using the following light time and aberration + corrections: NONE, LT and LT+S. Explain the differences. - 3. Extend, or modify, your program to compute the position of the - Sun as seen from Saturn in the J2000 frame (J2000), in - kilometers, using the following light time and aberration - corrections: NONE, LT and LT+S. Explain the differences. +#. Examine the CASSINI frames definition kernel and the ISS + instrument kernel to find the SPICE ID/name definitions. - 4. Examine the CASSINI frames definition kernel and the ISS - instrument kernel to find the SPICE ID/name definitions. +**Solutions and answers** -Solutions and answers +#. When running the original program without the Solar System + ephemerides SPK, an error is produced by :py:func:`spiceypy.spkezr `: -.. code-block:: text + .. code-block:: text - 1. When running the original program without the Solar System - ephemerides SPK, an error is produced by spiceypy.spkezr: - - Traceback (most recent call last): - File "getsta.py", line 128, in - getsta() - File "getsta.py", line 47, in getsta - 'LT+S', 'CASSINI' ) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 76, in with_errcheck - check_for_spice_error(f) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 59, in check_for_spice_error - raise stypes.SpiceyError(msg) spiceypy.utils.support_types.SpiceyError: - ===================================================================== - =========== + ================================================================================ - Toolkit version: N0066 + Toolkit version: N0067 SPICE(SPKINSUFFDATA) -- - Insufficient ephemeris data has been loaded to compute the state of - - 82 (CASSINI) relative to 0 (SOLAR SYSTEM BARYCENTER) at the ephemeris - epoch 2004 JUN 11 19:33:04.184. + Insufficient ephemeris data has been loaded to compute the state of -82 (CASSINI) relative to 0 (SOLAR SYSTEM BARYCENTER) at the ephemeris epoch 2004 JUN 11 19:33:04.184. spkezr_c --> SPKEZR --> SPKEZ --> SPKACS --> SPKGEO - ===================================================================== - =========== - - This error is generated when trying to compute the apparent - state of Phoebe as seen from CASSINI in the J2000 frame because - despite both Phoebe and CASSINI ephemeris data being relative - to the Saturn Barycenter, the state of the spacecraft with - respect to the solar system barycenter is required to compute - the light time and stellar aberrations. The loaded SPK data are - enough to compute geometric states of CASSINI with respect to - the Saturn Barycenter, and geometric states of Phoebe with - respect to the Saturn Barycenter, but insufficient to compute - the state of the spacecraft relative to the Solar System - Barycenter because the SPK data needed to compute geometric - states of Saturn Barycenter relative to the Solar System - barycenter are no longer loaded. Run "brief" on the SPKs used - in the original task to find out which ephemeris objects are - available from those kernels. If you want to find out what is - the 'center of motion' for the ephemeris object(s) included in - an SPK, use the -c option when running "brief": - + ================================================================================ + + This error is generated when trying to compute the apparent + state of Phoebe as seen from CASSINI in the J2000 frame because + despite both Phoebe and CASSINI ephemeris data being relative + to the Saturn Barycenter, the state of the spacecraft with + respect to the solar system barycenter is required to compute + the light time and stellar aberrations. The loaded SPK data are + enough to compute geometric states of CASSINI with respect to + the Saturn Barycenter, and geometric states of Phoebe with + respect to the Saturn Barycenter, but insufficient to compute + the state of the spacecraft relative to the Solar System + Barycenter because the SPK data needed to compute geometric + states of Saturn Barycenter relative to the Solar System + barycenter are no longer loaded. Run "brief" on the SPKs used + in the original task to find out which ephemeris objects are + available from those kernels. If you want to find out what is + the 'center of motion' for the ephemeris object(s) included in + an SPK, use the ``-c`` option when running "brief": + + .. code-block:: text BRIEF -- Version 4.0.0, September 8, 2010 -- Toolkit Version N0066 @@ -973,8 +701,7 @@ Solutions and answers EARTH (399) w.r.t. EARTH BARYCENTER (3) MARS (499) w.r.t. MARS BARYCENTER (4) Start of Interval (UTC) End of Interval (UTC) - ----------------------------- ------------------------- - ---- + ----------------------------- ----------------------------- 2004-JUN-11 05:00:00.000 2004-JUN-12 12:00:00.000 @@ -991,8 +718,7 @@ Solutions and answers PHOEBE (609) w.r.t. SATURN BARYCENTER (6) SATURN (699) w.r.t. SATURN BARYCENTER (6) Start of Interval (UTC) End of Interval (UTC) - ----------------------------- ------------------------- - ---- + ----------------------------- ----------------------------- 2004-JUN-11 05:00:00.000 2004-JUN-12 12:00:00.000 @@ -1000,71 +726,70 @@ Solutions and answers Body: CASSINI (-82) w.r.t. SATURN BARYCENTER (6) Start of Interval (UTC) End of Interval (UTC) - ----------------------------- --------------------------- - -- + ----------------------------- ----------------------------- 2004-JUN-11 05:00:00.000 2004-JUN-12 12:00:00.000 - - - 2. If you run your extended program with the original meta-kernel, - the SPICE(SPKINSUFFDATA) error should be produced by the - spiceypy.spkpos function because you have not loaded enough - ephemeris data to compute the position of Jupiter with respect - to Saturn. The loaded SPKs contain data for Saturn relative to - the Solar System Barycenter, and for the Jupiter System - Barycenter relative to the Solar System Barycenter, but the - data for Jupiter relative to the Jupiter System Barycenter are - missing: - - - Additional kernels required for this task: - - File name Contents - ----------------------- ---------------------------------- - jup310_2004.bsp Generic Jovian Satellite Ephemeris - - - available in the NAIF server at: - - https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/satellites/ - - - Download the relevant SPK, add it to the meta-kernel and run - again your extended program. The solution for the input UTC - time "2004 jun 11 19:32:00" when using the downloaded Jovian - Satellite Ephemeris SPK: - - Actual position of Jupiter as seen from Saturn in the - J2000 frame (km): - X = -436016583.291 - Y = -1094176737.323 - Z = -446585337.431 - - 3. When using 'NONE' aberration corrections, spiceypy.spkpos - returns the geometric position of the target body relative to - the observer. If 'LT' is used, the returned vector corresponds - to the position of the target at the moment it emitted photons - arriving at the observer at `et'. If 'LT+S' is used instead, - the returned vector takes into account the observer's velocity - relative to the solar system barycenter. The solution for the - input UTC time "2004 jun 11 19:32:00" is: - - - Actual (geometric) position of Sun as seen from Saturn in the - J2000 frame (km): - X = 367770592.367 - Y = -1197330367.359 - Z = -510369088.677 - Light-time corrected position of Sun as seen from Saturn in the - J2000 frame (km): - X = 367770572.921 - Y = -1197330417.733 - Z = -510369109.509 - Apparent position of Sun as seen from Saturn in the - J2000 frame (km): - X = 367726456.168 - Y = -1197342627.879 - Z = -510372252.747 +#. If you run your extended program with the original meta-kernel, + the ``SPICE(SPKINSUFFDATA)`` error should be produced by the + :py:func:`spiceypy.spkpos ` function because you have not loaded enough + ephemeris data to compute the position of Jupiter with respect + to Saturn. The loaded SPKs contain data for Saturn relative to + the Solar System Barycenter, and for the Jupiter System + Barycenter relative to the Solar System Barycenter, but the + data for Jupiter relative to the Jupiter System Barycenter are + missing: + + .. code-block:: text + + Additional kernels required for this task: + + File name Contents + ----------------------- ---------------------------------- + jup310_2004.bsp Generic Jovian Satellite Ephemeris + + available in the NAIF server at: + + https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/satellites/ + + Download the relevant SPK, add it to the meta-kernel and run + again your extended program. The solution for the input UTC + time "2004 jun 11 19:32:00" when using the downloaded Jovian + Satellite Ephemeris SPK: + + .. code-block:: text + + Actual position of Jupiter as seen from Saturn in the + J2000 frame (km): + X = -436016583.291 + Y = -1094176737.323 + Z = -446585337.431 + +#. When using ``'NONE'`` aberration corrections, :py:func:`spiceypy.spkpos ` + returns the geometric position of the target body relative to + the observer. If ``'LT'`` is used, the returned vector corresponds + to the position of the target at the moment it emitted photons + arriving at the observer at ``et``. If ``'LT+S'`` is used instead, + the returned vector takes into account the observer's velocity + relative to the solar system barycenter. The solution for the + input UTC time "2004 jun 11 19:32:00" is: + + .. code-block:: text + + Actual (geometric) position of Sun as seen from Saturn in the + J2000 frame (km): + X = 367770592.367 + Y = -1197330367.359 + Z = -510369088.677 + Light-time corrected position of Sun as seen from Saturn in the + J2000 frame (km): + X = 367770572.921 + Y = -1197330417.733 + Z = -510369109.509 + Apparent position of Sun as seen from Saturn in the + J2000 frame (km): + X = 367726456.168 + Y = -1197342627.879 + Z = -510372252.747 Spacecraft Orientation and Reference Frames (xform) --------------------------------------------------- @@ -1077,22 +802,20 @@ Task Statement Write a program that prompts the user for an input time string, computes and displays the following at the epoch of interest: -.. code-block:: text +#. The apparent state of Phoebe as seen from CASSINI in the + IAU_PHOEBE body-fixed frame. This vector itself is not of any + particular interest, but it is a useful intermediate quantity + in some geometry calculations. - 1. The apparent state of Phoebe as seen from CASSINI in the - IAU_PHOEBE body-fixed frame. This vector itself is not of any - particular interest, but it is a useful intermediate quantity - in some geometry calculations. +#. The angular separation between the apparent position of Earth + as seen from CASSINI and the nominal boresight of the CASSINI + high gain antenna (HGA). - 2. The angular separation between the apparent position of Earth - as seen from CASSINI and the nominal boresight of the CASSINI - high gain antenna (HGA). - - The HGA boresight direction is provided by the kernel variable - TKFRAME_-82101_BORESIGHT, which is defined in the Cassini frame - kernel cited above in the section "Kernels Used." In this - kernel, the HGA boresight vector is expressed relative to the - CASSINI_HGA reference frame. + The HGA boresight direction is provided by the kernel variable + TKFRAME\_-82101_BORESIGHT, which is defined in the Cassini frame + kernel cited above in the section “Kernels Used.” In this + kernel, the HGA boresight vector is expressed relative to the + CASSINI_HGA reference frame. Use the program to compute these quantities at the epoch “2004 jun 11 19:32:00” UTC. @@ -1105,7 +828,7 @@ Learning Goals Familiarity with the different types of kernels involved in chaining reference frames together, both inertial and non-inertial. Discover some of the matrix and vector math functions. Understand the difference -between spiceypy.pxform and spiceypy.sxform. +between :py:func:`spiceypy.pxform ` and :py:func:`spiceypy.sxform `. .. _approach-2: @@ -1115,60 +838,56 @@ Approach The solution to the problem can be broken down into a series of simple steps: -.. code-block:: text - - -- Decide which SPICE kernels are necessary. Prepare a meta-kernel - listing the kernels and load it into the program. +- Decide which SPICE kernels are necessary. Prepare a meta-kernel + listing the kernels and load it into the program. - -- Prompt the user for an input time string. +- Prompt the user for an input time string. - -- Convert the input time string into ephemeris time expressed as - seconds past J2000 TDB. +- Convert the input time string into ephemeris time expressed as + seconds past J2000 TDB. - -- Compute the state of Phoebe relative to CASSINI in the J2000 - reference frame, corrected for aberrations. +- Compute the state of Phoebe relative to CASSINI in the J2000 + reference frame, corrected for aberrations. - -- Compute the state transformation matrix from J2000 to - IAU_PHOEBE at the epoch, adjusted for light time. +- Compute the state transformation matrix from J2000 to + IAU_PHOEBE at the epoch, adjusted for light time. - -- Multiply the state of Phoebe relative to CASSINI in the J2000 - reference frame by the state transformation matrix computed in - the previous step. +- Multiply the state of Phoebe relative to CASSINI in the J2000 + reference frame by the state transformation matrix computed in + the previous step. - -- Compute the position of Earth relative to CASSINI in the J2000 - reference frame, corrected for aberrations. +- Compute the position of Earth relative to CASSINI in the J2000 + reference frame, corrected for aberrations. - -- Determine what the nominal boresight of the CASSINI high gain - antenna is by examining the frame kernel's content. +- Determine what the nominal boresight of the CASSINI high gain + antenna is by examining the frame kernel's content. - -- Compute the rotation matrix from the CASSINI high gain antenna - frame to J2000. +- Compute the rotation matrix from the CASSINI high gain antenna + frame to J2000. - -- Multiply the nominal boresight expressed in the CASSINI high - gain antenna frame by the rotation matrix from the previous - step. +- Multiply the nominal boresight expressed in the CASSINI high + gain antenna frame by the rotation matrix from the previous + step. - -- Compute the separation between the result of the previous step - and the apparent position of the Earth relative to CASSINI in - the J2000 frame. +- Compute the separation between the result of the previous step + and the apparent position of the Earth relative to CASSINI in + the J2000 frame. HINT: Several of the steps above may be compressed into a single step using SpiceyPy functions with which you are already familiar. The "long way" presented above is intended to facilitate the introduction -of the functions spiceypy.pxform and spiceypy.sxform. +of the functions :py:func:`spiceypy.pxform ` and :py:func:`spiceypy.sxform `. You may find it useful to consult the permuted index, the headers of various source modules, and the following toolkit documentation: -.. code-block:: text - - 1. Frames Required Reading (frames.req) +#. Frames Required Reading (frames.req) - 2. PCK Required Reading (pck.req) +#. PCK Required Reading (pck.req) - 3. SPK Required Reading (spk.req) +#. SPK Required Reading (spk.req) - 4. CK Required Reading (ck.req) +#. CK Required Reading (ck.req) This particular example makes use of many of the different types of SPICE kernels. You should spend a few moments thinking about which @@ -1179,233 +898,24 @@ kernels you will need and what data they provide. Solution ^^^^^^^^ -Solution Meta-Kernel +**Solution Meta-Kernel** The meta-kernel we created for the solution to this exercise is named 'xform.tm'. Its contents follow: -.. code-block:: text - - KPL/MK - - This is the meta-kernel used in the solution of the "Spacecraft - Orientation and Reference Frames" 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 +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/xform_make_mk.py - - \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' ) - \begintext - -Solution Source Code +**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() - -Solution Sample Output +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/xform.py + +**Solution Sample Output** Execute the program: @@ -1453,62 +963,44 @@ unlike the regular tasks, no approach or solution source code is provided. In the next section, you will find the numeric solutions (when applicable) and answers to the questions asked in these tasks. -Task statements and questions +**Task statements and questions** -.. code-block:: text +#. Run the original program using the input UTC time "2004 jun 11 + 18:25:00". Explain what happens. - 1. Run the original program using the input UTC time "2004 jun 11 - 18:25:00". Explain what happens. +#. Compute the angular separation between the apparent position of + the Sun as seen from CASSINI and the nominal boresight of the + CASSINI high gain antenna (HGA). Is the HGA illuminated? - 2. Compute the angular separation between the apparent position of - the Sun as seen from CASSINI and the nominal boresight of the - CASSINI high gain antenna (HGA). Is the HGA illuminated? +**Solutions and answers** -Solutions and answers +#. When running the original software using as input the UTC time + string "2004 jun 11 18:25:00": -.. code-block:: text + .. code-block:: text - 1. When running the original software using as input the UTC time - string "2004 jun 11 18:25:00": - - Traceback (most recent call last): - File "xform.py", line 183, in - xform() - File "xform.py", line 130, in xform - pform = spiceypy.pxform( 'CASSINI_HGA', 'J2000', et ) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 76, in with_errcheck - check_for_spice_error(f) - File "/home/bsemenov/local/lib/python3.5/site-packages/spiceypy/spi - ceypy.py", line 59, in check_for_spice_error - raise stypes.SpiceyError(msg) spiceypy.utils.support_types.SpiceyError: - ===================================================================== - =========== + ================================================================================ - Toolkit version: N0066 + Toolkit version: N0067 SPICE(NOFRAMECONNECT) -- - At epoch 1.4025036418463E+08 TDB (2004 JUN 11 18:26:04.184 TDB), ther - e is insufficient information available to transform from reference f - rame -82101 (CASSINI_HGA) to reference frame 1 (J2000). Frame CASSINI - _HGA could be transformed to frame -82000 (CASSINI_SC_COORD). The lat - ter is a CK frame; a CK file containing data + At epoch 1.4025036418463E+08 TDB (2004 JUN 11 18:26:04.184 TDB), there is insufficient information available to transform from reference frame -82101 (CASSINI_HGA) to reference frame 1 (J2000). Frame CASSINI_HGA could be transformed to frame -82000 (CASSINI_SC_COORD). The latter is a CK frame; a CK file containing data pxform_c --> PXFORM --> REFCHG - ===================================================================== - =========== + ================================================================================ - spiceypy.pxform returns the SPICE(NOFRAMECONNECT) error, which - indicates that there are not sufficient data to perform the - transformation from the CASSINI_HGA frame to J2000 at the - requested epoch. If you summarize the CASSINI spacecraft CK - using the "ckbrief" utility program with the -dump option - (display interpolation intervals boundaries) you will find that - the CK contains gaps within its segment: + :py:func:`spiceypy.pxform ` returns the ``SPICE(NOFRAMECONNECT)`` error, which + indicates that there are not sufficient data to perform the + transformation from the CASSINI_HGA frame to J2000 at the + requested epoch. If you summarize the CASSINI spacecraft CK + using the "ckbrief" utility program with the ``-dump`` option + (display interpolation intervals boundaries) you will find that + the CK contains gaps within its segment: + .. code-block:: text CKBRIEF -- Version 6.1.0, June 27, 2014 -- Toolkit Version N0066 @@ -1526,12 +1018,11 @@ Solutions and answers 2004-JUN-12 05:54:56.012 2004-JUN-12 10:32:08.016 Y 2004-JUN-12 10:33:26.016 2004-JUN-12 11:59:59.998 Y + whereas if you had used ckbrief without ``-dump`` you would have + gotten the following information (only CK segment begin/end + times): - - whereas if you had used ckbrief without -dump you would have - gotten the following information (only CK segment begin/end - times): - + .. code-block:: text CKBRIEF -- Version 6.1.0, June 27, 2014 -- Toolkit Version N0066 @@ -1543,16 +1034,16 @@ Solutions and answers ------------------------ ------------------------ --- 2004-JUN-11 05:00:00.000 2004-JUN-12 11:59:59.998 Y + which has insufficient detail to reveal the problem. +#. By computing the apparent position of the Sun as seen from + CASSINI in the CASSINI_HGA frame, and the angular separation + between this vector and the nominal boresight of the CASSINI + high gain antenna (+Z-axis of the CASSINI_HGA frame), you will + find whether the HGA is illuminated. The solution for the input + UTC time "2004 jun 11 19:32:00" is: - which has insufficient detail to reveal the problem. - - 2. By computing the apparent position of the Sun as seen from - CASSINI in the CASSINI_HGA frame, and the angular separation - between this vector and the nominal boresight of the CASSINI - high gain antenna (+Z-axis of the CASSINI_HGA frame), you will - find whether the HGA is illuminated. The solution for the input - UTC time "2004 jun 11 19:32:00" is: + .. code-block:: text Angular separation between the apparent position of the Sun and the nominal boresight of the CASSINI high gain antenna (degrees): @@ -1561,7 +1052,7 @@ Solutions and answers HGA illumination: CASSINI high gain antenna IS illuminated. - since the angular separation is smaller than 90 degrees. + since the angular separation is smaller than 90 degrees. Computing Sub-s/c and Sub-solar Points on an Ellipsoid and a DSK (subpts) ------------------------------------------------------------------------- @@ -1574,28 +1065,15 @@ Task Statement Write a program that prompts the user for an input UTC time string and computes the following quantities at that epoch: -.. code-block:: text - - 1. The apparent sub-observer point of CASSINI on Phoebe, in the - body fixed frame IAU_PHOEBE, in kilometers. +#. The apparent sub-observer point of CASSINI on Phoebe, in the + body fixed frame IAU_PHOEBE, in kilometers. - 2. The apparent sub-solar point on Phoebe, as seen from CASSINI in - the body fixed frame IAU_PHOEBE, in kilometers. +#. The apparent sub-solar point on Phoebe, as seen from CASSINI in + the body fixed frame IAU_PHOEBE, in kilometers. The program computes each point twice: once using an ellipsoidal shape -model and the - -.. code-block:: text - - near point/ellipsoid - -definition, and once using a DSK shape model and the - -.. code-block:: text - - nadir/dsk/unprioritized - -definition. +model and the ``near point/ellipsoid`` definition, and once using a DSK +shape model and the ``nadir/dsk/unprioritized`` definition. The program displays the results. Use the program to compute these quantities at “2004 jun 11 19:32:00” UTC. @@ -1619,17 +1097,7 @@ to understand how to call them. One point worth considering: how would the results change if the sub-solar and sub-observer points were computed using the - -.. code-block:: text - - intercept/ellipsoid - -and - -.. code-block:: text - - intercept/dsk/unprioritized - +``intercept/ellipsoid`` and ``intercept/dsk/unprioritized`` definitions? Which definition is appropriate? .. _solution-3: @@ -1637,152 +1105,24 @@ definitions? Which definition is appropriate? Solution ^^^^^^^^ -Solution Meta-Kernel +**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. +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/subpts_make_mk.py - 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 - -Solution Source Code +**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() - -Solution Sample Output +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/subpts.py + +**Solution Sample Output** Execute the program: @@ -1823,114 +1163,116 @@ Execute the program: .. _extra-credit-3: Extra Credit -^^^^^^^^^^^^^ +^^^^^^^^^^^^ In this "extra credit" section you will be presented with more -complex tasks, aimed at improving your understanding of spiceypy.subpnt -and spiceypy.subslr functions. +complex tasks, aimed at improving your understanding of :py:func:`spiceypy.subpnt ` +and :py:func:`spiceypy.subslr ` functions. These "extra credit" tasks are provided as task statements, and unlike the regular tasks, no approach or solution source code is provided. In the next section, you will find the numeric solutions (when applicable) and answers to the questions asked in these tasks. -Task statements and questions - -.. code-block:: text - - 1. Recompute the apparent sub-solar point on Phoebe as seen from - CASSINI in the body fixed frame IAU_PHOEBE in kilometers using - the 'Intercept/ellipsoid' method at "2004 jun 11 19:32:00". - Explain the differences. - - 2. Compute the geometric sub-spacecraft point of CASSINI on Phoebe - in the body fixed frame IAU_PHOEBE in kilometers using the - 'Near point/ellipsoid' method at "2004 jun 11 19:32:00". - - 3. Transform the sub-spacecraft Cartesian coordinates obtained in - the previous task to planetocentric and planetographic - coordinates. When computing planetographic coordinates, - retrieve Phoebe's radii by calling spiceypy.bodvrd and use the - first element of the returned radii values as Phoebe's - equatorial radius. Explain why planetocentric and - planetographic latitudes and longitudes are different. Explain - why the planetographic altitude for a point on the surface of - Phoebe is not zero and whether this is correct or not. - -Solutions and answers - -.. code-block:: text - - 1. The differences observed are due to the computation method. The - "Intercept/ellipsoid" method defines the sub-solar point as - the target surface intercept of the line containing the Sun and - the target's center, while the "Near point/ellipsoid" method - defines the sub-solar point as the the nearest point on the - target relative to the Sun. Since Phoebe is not spherical, - these two points are not the same: - - Apparent sub-solar point on Phoebe as seen from CASSINI in - the IAU_PHOEBE frame using the 'Near Point: ellipsoid' method - (km): - X = 78.681 - Y = 76.879 - Z = -21.885 - - Apparent sub-solar point on Phoebe as seen from CASSINI in - the IAU_PHOEBE frame using the 'Intercept: ellipsoid' method - (km): - X = 74.542 - Y = 79.607 - Z = -24.871 - - 2. The geometric sub-spacecraft point of CASSINI on Phoebe in the - body fixed frame IAU_PHOEBE in kilometers at "2004 jun 11 - 19:32:00" UTC epoch is: - - Geometric sub-spacecraft point of CASSINI on Phoebe in - the IAU_PHOEBE frame using the 'Near Point: ellipsoid' method - (km): - X = 104.497 - Y = 45.270 - Z = 7.384 - - 3. The sub-spacecraft point of CASSINI on Phoebe in planetocentric - and planetographic coordinates at "2004 jun 11 19:32:00" UTC - epoch is: - - Planetocentric coordinates of the CASSINI - sub-spacecraft point on Phoebe (degrees, km): - LAT = 3.710 - LON = 23.423 - R = 114.121 - - Planetographic coordinates of the CASSINI - sub-spacecraft point on Phoebe (degrees, km): - LAT = 4.454 - LON = 336.577 - ALT = -0.831 - - The planetocentric and planetographic longitudes are different - ("graphic" = 360 - "centric") because planetographic - longitudes on Phoebe are measured positive west as defined by - Phoebe's rotation direction. - - The planetocentric and planetographic latitudes are different - because the planetocentric latitude was computed as the angle - between the direction from the center of the body to the point - and the equatorial plane, while the planetographic latitude was - computed as the angle between the surface normal at the point - and the equatorial plane. - - The planetographic altitude is non zero because it was computed - using a different and incorrect Phoebe surface model: a - spheroid with equal equatorial radii. The surface point - returned by spiceypy.subpnt was computed by treating Phoebe as - a triaxial ellipsoid with different equatorial radii. The - planetographic latitude is also incorrect because it is based - on the normal to the surface of the spheroid rather than the - ellipsoid, In general planetographic coordinates cannot be used - for bodies with shapes modeled as triaxial ellipsoids. +**Task statements and questions** + +#. Recompute the apparent sub-solar point on Phoebe as seen from + CASSINI in the body fixed frame IAU_PHOEBE in kilometers using + the 'Intercept/ellipsoid' method at "2004 jun 11 19:32:00". + Explain the differences. + +#. Compute the geometric sub-spacecraft point of CASSINI on Phoebe + in the body fixed frame IAU_PHOEBE in kilometers using the + 'Near point/ellipsoid' method at "2004 jun 11 19:32:00". + +#. Transform the sub-spacecraft Cartesian coordinates obtained in + the previous task to planetocentric and planetographic + coordinates. When computing planetographic coordinates, + retrieve Phoebe's radii by calling :py:func:`spiceypy.bodvrd ` and use the + first element of the returned radii values as Phoebe's + equatorial radius. Explain why planetocentric and + planetographic latitudes and longitudes are different. Explain + why the planetographic altitude for a point on the surface of + Phoebe is not zero and whether this is correct or not. + +**Solutions and answers** + +#. The differences observed are due to the computation method. The + "Intercept/ellipsoid" method defines the sub-solar point as + the target surface intercept of the line containing the Sun and + the target's center, while the "Near point/ellipsoid" method + defines the sub-solar point as the nearest point on the + target relative to the Sun. Since Phoebe is not spherical, + these two points are not the same: + + .. code-block:: text + + Apparent sub-solar point on Phoebe as seen from CASSINI in + the IAU_PHOEBE frame using the 'Near Point: ellipsoid' method + (km): + X = 78.681 + Y = 76.879 + Z = -21.885 + + Apparent sub-solar point on Phoebe as seen from CASSINI in + the IAU_PHOEBE frame using the 'Intercept: ellipsoid' method + (km): + X = 74.542 + Y = 79.607 + Z = -24.871 + +#. The geometric sub-spacecraft point of CASSINI on Phoebe in the + body fixed frame IAU_PHOEBE in kilometers at "2004 jun 11 + 19:32:00" UTC epoch is: + + .. code-block:: text + + Geometric sub-spacecraft point of CASSINI on Phoebe in + the IAU_PHOEBE frame using the 'Near Point: ellipsoid' method + (km): + X = 104.497 + Y = 45.270 + Z = 7.384 + +#. The sub-spacecraft point of CASSINI on Phoebe in planetocentric + and planetographic coordinates at "2004 jun 11 19:32:00" UTC + epoch is: + + .. code-block:: text + + Planetocentric coordinates of the CASSINI + sub-spacecraft point on Phoebe (degrees, km): + LAT = 3.710 + LON = 23.423 + R = 114.121 + + Planetographic coordinates of the CASSINI + sub-spacecraft point on Phoebe (degrees, km): + LAT = 4.454 + LON = 336.577 + ALT = -0.831 + + The planetocentric and planetographic longitudes are different + ("graphic" = 360 - "centric") because planetographic + longitudes on Phoebe are measured positive west as defined by + Phoebe's rotation direction. + + The planetocentric and planetographic latitudes are different + because the planetocentric latitude was computed as the angle + between the direction from the center of the body to the point + and the equatorial plane, while the planetographic latitude was + computed as the angle between the surface normal at the point + and the equatorial plane. + + The planetographic altitude is non zero because it was computed + using a different and incorrect Phoebe surface model: a + spheroid with equal equatorial radii. The surface point + returned by :py:func:`spiceypy.subpnt ` was computed by treating Phoebe as + a triaxial ellipsoid with different equatorial radii. The + planetographic latitude is also incorrect because it is based + on the normal to the surface of the spheroid rather than the + ellipsoid. In general planetographic coordinates cannot be used + for bodies with shapes modeled as triaxial ellipsoids. Intersecting Vectors with an Ellipsoid and a DSK (fovint) --------------------------------------------------------- @@ -1947,12 +1289,10 @@ Phoebe. Compute each intercept twice: once with Phoebe's shape modeled as an ellipsoid, and once with Phoebe's shape modeled by DSK data. The program presents each point of intersection as -.. code-block:: text - - 1. A Cartesian vector in the IAU_PHOEBE frame +#. A Cartesian vector in the IAU_PHOEBE frame - 2. Planetocentric (latitudinal) coordinates in the IAU_PHOEBE - frame. +#. Planetocentric (latitudinal) coordinates in the IAU_PHOEBE + frame. For each of the camera FOV boundary and boresight vectors, if an intersection is found, the program displays the results of the above @@ -1960,13 +1300,11 @@ computations, otherwise it indicates no intersection exists. At each point of intersection compute the following: -.. code-block:: text - - 3. Phase angle +3. Phase angle - 4. Solar incidence angle +4. Solar incidence angle - 5. Emission angle +5. Emission angle These angles should be computed using both ellipsoidal and DSK shape models. @@ -1975,11 +1313,8 @@ Additionally compute the local solar time at the intercept of the camera boresight with the surface of Phoebe, using both ellipsoidal and DSK shape models. -Use this program to compute values at the epoch: - -.. code-block:: text - - "2004 jun 11 19:32:00" UTC +Use this program to compute values at the epoch +``"2004 jun 11 19:32:00"`` UTC. .. _learning-goals-4: @@ -2000,47 +1335,41 @@ Approach This problem can be broken down into several simple, small steps: -.. code-block:: text - - -- Decide which SPICE kernels are necessary. Prepare a meta-kernel - listing the kernels and load it into the program. Remember, you - will need to find a kernel with information about the CASSINI - NAC camera. +- Decide which SPICE kernels are necessary. Prepare a meta-kernel + listing the kernels and load it into the program. Remember, you + will need to find a kernel with information about the CASSINI + NAC camera. - -- Prompt the user for an input time string. +- Prompt the user for an input time string. - -- Convert the input time string into ephemeris time expressed as - seconds past J2000 TDB. +- Convert the input time string into ephemeris time expressed as + seconds past J2000 TDB. - -- Retrieve the FOV (field of view) configuration for the CASSINI - NAC camera. +- Retrieve the FOV (field of view) configuration for the CASSINI + NAC camera. For each vector in the set of boundary corner vectors, and for the boresight vector, perform the following operations: -.. code-block:: text +- Compute the intercept of the vector with Phoebe modeled as an + ellipsoid or using DSK data - -- Compute the intercept of the vector with Phoebe modeled as an - ellipsoid or using DSK data +- If this intercept is found, convert the position vector of the + intercept into planetocentric coordinates. - -- If this intercept is found, convert the position vector of the - intercept into planetocentric coordinates. + Then compute the phase, solar incidence, and emission angles at + the intercept. Otherwise indicate to the user no intercept was + found for this vector. - Then compute the phase, solar incidence, and emission angles at - the intercept. Otherwise indicate to the user no intercept was - found for this vector. - - -- Compute the planetocentric longitude of the boresight - intercept. +- Compute the planetocentric longitude of the boresight + intercept. Finally -.. code-block:: text - - -- Compute the local solar time at the boresight intercept - longitude on a 24-hour clock. The input time for this - computation should be the TDB observation epoch minus one-way - light time from the boresight intercept to the spacecraft. +- Compute the local solar time at the boresight intercept + longitude on a 24-hour clock. The input time for this + computation should be the TDB observation epoch minus one-way + light time from the boresight intercept to the spacecraft. It may be useful to consult the CASSINI ISS instrument kernel to determine the name of the NAC camera as well as its configuration. This @@ -2052,287 +1381,24 @@ the "Spacecraft Orientation and Reference Frames" task. Solution ^^^^^^^^ -Solution Meta-Kernel +**Solution Meta-Kernel** The meta-kernel we created for the solution to this exercise is named 'fovint.tm'. Its contents follow: -.. code-block:: text +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/fovint_make_mk.py - 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 - -Solution Source Code +**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() - -Solution Sample Output +.. py-editor:: + :env: rsenv + :src: scripts/remote_sensing/fovint.py + +**Solution Sample Output** Execute the program: @@ -2511,6 +1577,6 @@ Execute the program: .. _extra-credit-4: Extra Credit -^^^^^^^^^^^^^ +^^^^^^^^^^^^ There are no "extra credit" tasks for this step of the lesson. diff --git a/docs/scripts/binary_pck/erotat.py b/docs/scripts/binary_pck/erotat.py new file mode 100644 index 00000000..b49023a0 --- /dev/null +++ b/docs/scripts/binary_pck/erotat.py @@ -0,0 +1,175 @@ +import spiceypy + + +def erotat(): + METAKR = "erotat.tm" + + x = [1.0, 0.0, 0.0] + z = [0.0, 0.0, 1.0] + + spiceypy.furnsh(METAKR) + + timstr = "2007 JAN 1 00:00:00" + et = spiceypy.str2et(timstr) + + # + # Look up the apparent position of the Moon relative + # to the Earth's center in the IAU_EARTH frame at ET. + # + [lmoonv, ltime] = spiceypy.spkpos("moon", et, "iau_earth", "lt+s", "earth") + # + # Express the Moon direction in terms of longitude + # and latitude in the IAU_EARTH frame. + # + [r, lon, lat] = spiceypy.reclat(lmoonv) + + print( + f"Earth-Moon direction using low accuracy\n" + f"PCK and IAU_EARTH frame:\n" + f"Moon lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Moon lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Look up the apparent position of the Moon relative + # to the Earth's center in the ITRF93 frame at ET. + # + [hmoonv, ltime] = spiceypy.spkpos("moon", et, "ITRF93", "lt+s", "earth") + # + # Express the Moon direction in terms of longitude + # and latitude in the ITRF93 frame. + # + [r, lon, lat] = spiceypy.reclat(hmoonv) + + print( + f"Earth-Moon direction using high accuracy\n" + f"PCK and ITRF93 frame:\n" + f"Moon lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Moon lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Find the angular separation of the Moon position + # vectors in degrees. + # + sep = spiceypy.dpr() * spiceypy.vsep(lmoonv, hmoonv) + + print(f"Earth-Moon vector separation angle (deg): {sep:15.6f}\n") + + # + # Next, express the +Z and +X axes of the ITRF93 frame in + # the IAU_EARTH frame. We'll do this for two times: et + # and et + 100 days. + # + for i in range(2): + t = et + i * spiceypy.spd() * 100 + + outstr = spiceypy.timout(t, "YYYY-MON-DD HR:MN:SC.### (UTC)") + + print(f"Epoch: {outstr:s}") + + # + # Find the rotation matrix for conversion of + # position vectors from the IAU_EARTH to the + # ITRF93 frame. + # + rmat = spiceypy.pxform("iau_earth", "itrf93", t) + itrfx = rmat[0] + itrfz = rmat[2] + + # + # Display the angular offsets of the ITRF93 + # +X and +Z axes from their IAU_EARTH counterparts. + # + sep = spiceypy.vsep(itrfx, x) + + print(f"ITRF93 - IAU_EARTH +X axis separation angle (deg): {sep * spiceypy.dpr():13.6f}") + + sep = spiceypy.vsep(itrfz, z) + + print(f"ITRF93 - IAU_EARTH +Z axis separation angle (deg): {sep * spiceypy.dpr():13.6f}\n") + + # + # Find the azimuth and elevation of apparent + # position of the Moon in the local topocentric + # reference frame at the DSN station DSS-13. + # First look up the Moon's position relative to the + # station in that frame. + # + [topov, ltime] = spiceypy.spkpos("moon", et, "DSS-13_TOPO", "lt+s", "DSS-13") + + # + # Express the station-moon direction in terms of longitude + # and latitude in the DSS-13_TOPO frame. + # + [r, lon, lat] = spiceypy.reclat(topov) + + # + # Convert to azimuth-elevation. + # + az = -lon + + if az < 0.0: + az += spiceypy.twopi() + + el = lat + + print( + f"DSS-13-Moon az/el using high accuracy PCK and DSS-13_TOPO frame:\n" + f"Moon Az (deg): {az * spiceypy.dpr():15.6f}\n" + f"Moon El (deg): {el * spiceypy.dpr():15.6f}\n" + ) + + # + # Find the sub-solar point on the Earth at ET using the + # Earth body-fixed frame IAU_EARTH. Treat the Sun as + # the observer. + # + [lsub, trgepc, srfvec] = spiceypy.subslr( + "near point: ellipsoid", "earth", et, "IAU_EARTH", "lt+s", "sun" + ) + + # + # Display the sub-point in latitudinal coordinates. + # + [r, lon, lat] = spiceypy.reclat(lsub) + + print( + f"Sub-Solar point on Earth using low accuracy\n" + f"PCK and IAU_EARTH frame:\n" + f"Sub-Solar lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Sub-Solar lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + + # + # Find the sub-solar point on the Earth at ET using the + # Earth body-fixed frame ITRF93. Treat the Sun as + # the observer. + # + [hsub, trgepc, srfvec] = spiceypy.subslr( + "near point: ellipsoid", "earth", et, "ITRF93", "lt+s", "sun" + ) + + # + # Display the sub-point in latitudinal coordinates. + # + [r, lon, lat] = spiceypy.reclat(hsub) + + print( + f"Sub-Solar point on Earth using high accuracy\n" + f"PCK and ITRF93 frame:\n" + f"Sub-Solar lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Sub-Solar lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + + # + # Find the distance between the sub-solar point + # vectors in km. + # + dist = spiceypy.vdist(lsub, hsub) + + print(f"Distance between sub-solar points (km): {dist:15.6f}") + + spiceypy.unload(METAKR) + + +if __name__ == "__main__": + erotat() diff --git a/docs/scripts/binary_pck/erotat_make_mk.py b/docs/scripts/binary_pck/erotat_make_mk.py new file mode 100644 index 00000000..85cb0568 --- /dev/null +++ b/docs/scripts/binary_pck/erotat_make_mk.py @@ -0,0 +1,33 @@ +mk = r""" +KPL/MK + +Meta-kernel for the "Earth Rotation" task +in the Binary PCK 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 +de414_2000_2020.bsp Solar System Ephemeris +earthstns_itrf93_050714.bsp DSN station Ephemeris +earth_topo_050714.tf Earth topocentric FK +pck00008.tpc NAIF text PCK +earth_000101_070725_070503.bpc Earth binary PCK + + +\begindata + +KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls' + 'kernels/spk/de414_2000_2020.bsp' + 'kernels/spk/earthstns_itrf93_050714.bsp' + 'kernels/fk/earth_topo_050714.tf' + 'kernels/pck/pck00008.tpc' + 'kernels/pck/earth_000101_070725_070503.bpc' ) + +\begintext +""" +with open("erotat.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file erotat.tm") diff --git a/docs/scripts/binary_pck/mrotat.py b/docs/scripts/binary_pck/mrotat.py new file mode 100644 index 00000000..f72ad499 --- /dev/null +++ b/docs/scripts/binary_pck/mrotat.py @@ -0,0 +1,129 @@ +import spiceypy + + +def mrotat(): + METAKR = "mrotat.tm" + + spiceypy.furnsh(METAKR) + + timstr = "2007 JAN 1 00:00:00" + et = spiceypy.str2et(timstr) + + # + # Look up the apparent position of the Earth relative + # to the Moon's center in the IAU_MOON frame at ET. + # + [imoonv, ltime] = spiceypy.spkpos("earth", et, "iau_moon", "lt+s", "moon") + + # + # Express the Earth direction in terms of longitude + # and latitude in the IAU_MOON frame. + # + [r, lon, lat] = spiceypy.reclat(imoonv) + + print( + f"\n" + f"Moon-Earth direction using low accuracy\n" + f"PCK and IAU_MOON frame:\n" + f"Earth lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Earth lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Look up the apparent position of the Earth relative + # to the Moon's center in the MOON_ME frame at ET. + # + [mmoonv, ltime] = spiceypy.spkpos("earth", et, "moon_me", "lt+s", "moon") + # + # Express the Earth direction in terms of longitude + # and latitude in the MOON_ME frame. + # + [r, lon, lat] = spiceypy.reclat(mmoonv) + + print( + f"Moon-Earth direction using high accuracy\n" + f"PCK and MOON_ME frame:\n" + f"Earth lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Earth lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Find the angular separation of the Earth position + # vectors in degrees. + # + sep = spiceypy.dpr() * spiceypy.vsep(imoonv, mmoonv) + + print("For IAU_MOON vs MOON_ME frames:") + print(f"Moon-Earth vector separation angle (deg): {sep:15.6f}\n") + # + # Look up the apparent position of the Earth relative + # to the Moon's center in the MOON_PA frame at ET. + # + [pmoonv, ltime] = spiceypy.spkpos("earth", et, "moon_pa", "lt+s", "moon") + # + # Express the Earth direction in terms of longitude + # and latitude in the MOON_PA frame. + # + [r, lon, lat] = spiceypy.reclat(pmoonv) + + print( + f"Moon-Earth direction using high accuracy\n" + f"PCK and MOON_PA frame:\n" + f"Earth lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Earth lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Find the angular separation of the Earth position + # vectors in degrees. + # + sep = spiceypy.dpr() * spiceypy.vsep(pmoonv, mmoonv) + + print("For MOON_PA vs MOON_ME frames:") + print(f"Moon-Earth vector separation angle (deg): {sep:15.6f}\n") + # + # Find the apparent sub-Earth point on the Moon at ET + # using the MOON_ME frame. + # + [msub, trgepc, srfvec] = spiceypy.subpnt( + "near point: ellipsoid", "moon", et, "moon_me", "lt+s", "earth" + ) + # + # Display the sub-point in latitudinal coordinates. + # + [r, lon, lat] = spiceypy.reclat(msub) + + print( + f"Sub-Earth point on Moon using high accuracy\n" + f"PCK and MOON_ME frame:\n" + f"Sub-Earth lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Sub-Earth lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Find the apparent sub-Earth point on the Moon at + # ET using the MOON_PA frame. + # + [psub, trgepc, srfvec] = spiceypy.subpnt( + "near point: ellipsoid", "moon", et, "moon_pa", "lt+s", "earth" + ) + # + # Display the sub-point in latitudinal coordinates. + # + [r, lon, lat] = spiceypy.reclat(psub) + + print( + f"Sub-Earth point on Moon using high accuracy\n" + f"PCK and MOON_PA frame:\n" + f"Sub-Earth lon (deg): {lon * spiceypy.dpr():15.6f}\n" + f"Sub-Earth lat (deg): {lat * spiceypy.dpr():15.6f}\n" + ) + # + # Find the distance between the sub-Earth points + # in km. + # + dist = spiceypy.vdist(msub, psub) + + print(f"Distance between sub-Earth points (km): {dist:15.6f}\n") + + spiceypy.unload(METAKR) + + +if __name__ == "__main__": + mrotat() diff --git a/docs/scripts/binary_pck/mrotat_make_mk.py b/docs/scripts/binary_pck/mrotat_make_mk.py new file mode 100644 index 00000000..c68cf133 --- /dev/null +++ b/docs/scripts/binary_pck/mrotat_make_mk.py @@ -0,0 +1,29 @@ +mk = r""" +KPL/MK + +Meta-kernel for the "Moon Rotation" task in the Binary PCK +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 +de414_2000_2020.bsp Solar System Ephemeris +moon_060721.tf Lunar FK +pck00008.tpc NAIF text PCK +moon_pa_de403_1950-2198.bpc Moon binary PCK + +\begindata + + KERNELS_TO_LOAD = ( 'kernels/lsk/naif0008.tls' + 'kernels/spk/de414_2000_2020.bsp' + 'kernels/fk/moon_060721.tf' + 'kernels/pck/pck00008.tpc' + 'kernels/pck/moon_pa_de403_1950-2198.bpc' ) +\begintext +""" +with open("mrotat.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file mrotat.tm") diff --git a/docs/scripts/event_finding/viewpr.py b/docs/scripts/event_finding/viewpr.py new file mode 100644 index 00000000..47260ef9 --- /dev/null +++ b/docs/scripts/event_finding/viewpr.py @@ -0,0 +1,172 @@ +# +# Solution viewpr +# +import spiceypy + +def viewpr(): + # + # Local Parameters + # + METAKR = "viewpr.tm" + TDBFMT = "YYYY MON DD HR:MN:SC.### (TDB) ::TDB" + MAXIVL = 1000 + MAXWIN = 2 * MAXIVL + # + # Load the meta-kernel. + # + spiceypy.furnsh(METAKR) + # + # Assign the inputs for our search. + # + # Since we're interested in the apparent location of the + # target, we use light time and stellar aberration + # corrections. We use the "converged Newtonian" form + # of the light time correction because this choice may + # increase the accuracy of the occultation times we'll + # compute using gfoclt. + # + srfpt = "DSS-14" + obsfrm = "DSS-14_TOPO" + target = "MEX" + abcorr = "CN+S" + start = "2004 MAY 2 TDB" + stop = "2004 MAY 6 TDB" + elvlim = 6.0 + # + # The elevation limit above has units of degrees; we convert + # this value to radians for computation using SPICE routines. + # We'll store the equivalent value in radians in revlim. + # + revlim = spiceypy.rpd() * elvlim + # + # Since SPICE doesn't directly support the AZ/EL coordinate + # system, we use the equivalent constraint + # + # latitude > revlim + # + # in the latitudinal coordinate system, where the reference + # frame is topocentric and is centered at the viewing location. + # + crdsys = "LATITUDINAL" + coord = "LATITUDE" + relate = ">" + # + # The adjustment value only applies to absolute extrema + # searches; simply give it an initial value of zero + # for this inequality search. + # + adjust = 0.0 + # + # stepsz is the step size, measured in seconds, used to search + # for times bracketing a state transition. Since we don't expect + # any events of interest to be shorter than five minutes, and + # since the separation between events is well over 5 minutes, + # we'll use this value as our step size. Units are seconds. + # + stepsz = 300.0 + # + # Display a banner for the output report: + # + print("\nInputs for target visibility search:\n") + print(f" Target = {target}") + print(f" Observation surface location = {srfpt}") + print(f" Observer's reference frame = {obsfrm}") + print(f" Elevation limit (degrees) = {elvlim}") + print(f" Aberration correction = {abcorr}") + print(f" Step size (seconds) = {stepsz}") + # + # Convert the start and stop times to ET. + # + etbeg = spiceypy.str2et(start) + etend = spiceypy.str2et(stop) + # + # Display the search interval start and stop times + # using the format shown below. + # + # 2004 MAY 06 20:15:00.000 (TDB) + # + timstr = spiceypy.timout(etbeg, TDBFMT) + print(f" Start time = {timstr}") + timstr = spiceypy.timout(etend, TDBFMT) + print(f" Stop time = {timstr}") + print(" ") + # + # Initialize the "confinement" window with the interval + # over which we'll conduct the search. + # + cnfine = spiceypy.cell_double(2) + spiceypy.wninsd(etbeg, etend, cnfine) + # + # In the call below, the maximum number of window + # intervals gfposc can store internally is set to MAXIVL. + # We set the cell size to MAXWIN to achieve this. + # + riswin = spiceypy.cell_double(MAXWIN) + # + # Now search for the time period, within our confinement + # window, during which the apparent target has elevation + # at least equal to the elevation limit. + # + spiceypy.gfposc( + target, + obsfrm, + abcorr, + srfpt, + crdsys, + coord, + relate, + revlim, + adjust, + stepsz, + MAXIVL, + cnfine, + riswin, + ) + # + # The function wncard returns the number of intervals + # in a SPICE window. + # + winsiz = spiceypy.wncard(riswin) + if winsiz == 0: + print("No events were found.") + else: + # + # Display the visibility time periods. + # + print(f"Visibility times of {target} as seen from {srfpt}:\n") + for i in range(winsiz): + # + # Fetch the start and stop times of + # the ith interval from the search result + # window riswin. + # + [intbeg, intend] = spiceypy.wnfetd(riswin, i) + # + # Convert the rise time to a TDB calendar string. + # + timstr = spiceypy.timout(intbeg, TDBFMT) + # + # Write the string to standard output. + # + if i == 0: + print(f"Visibility or window start time: {timstr}") + else: + print(f"Visibility start time: {timstr}") + # + # Convert the set time to a TDB calendar string. + # + timstr = spiceypy.timout(intend, TDBFMT) + # + # Write the string to standard output. + # + if i == (winsiz - 1): + print(f"Visibility or window stop time: {timstr}") + else: + print(f"Visibility stop time: {timstr}") + print(" ") + + spiceypy.unload(METAKR) + + +if __name__ == "__main__": + viewpr() diff --git a/docs/scripts/event_finding/viewpr_make_mk.py b/docs/scripts/event_finding/viewpr_make_mk.py new file mode 100644 index 00000000..69f9a8fa --- /dev/null +++ b/docs/scripts/event_finding/viewpr_make_mk.py @@ -0,0 +1,35 @@ +mk = r""" +KPL/MK + Example meta-kernel for geometric event finding hands-on + coding lesson. + Version 2.0.0 13-JUL-2017 (JDR) + The names and contents of the kernels referenced by this + meta-kernel are as follows: + File Name Description + ------------------------------ ------------------------------ + de405xs.bsp Planetary ephemeris SPK, + subsetted to cover only + time range of interest. + earthstns_itrf93_050714.bsp DSN station SPK. + earth_topo_050714.tf DSN station frame definitions. + earth_000101_060525_060303.bpc Binary PCK for Earth. + naif0008.tls Generic LSK. + ORMM__040501000000_00076XS.BSP MEX Orbiter trajectory SPK, + subsetted to cover only + time range of interest. + pck00008.tpc Generic PCK. +\begindata + KERNELS_TO_LOAD = ( + 'kernels/spk/de405xs.bsp' + 'kernels/spk/earthstns_itrf93_050714.bsp' + 'kernels/fk/earth_topo_050714.tf' + 'kernels/pck/earth_000101_060525_060303.bpc' + 'kernels/lsk/naif0008.tls' + 'kernels/spk/ORMM__040501000000_00076XS.BSP' + 'kernels/pck/pck00008.tpc' + ) +\begintext +""" +with open("viewpr.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file viewpr.tm") diff --git a/docs/scripts/event_finding/visibl.py b/docs/scripts/event_finding/visibl.py new file mode 100644 index 00000000..6e6e6d40 --- /dev/null +++ b/docs/scripts/event_finding/visibl.py @@ -0,0 +1,222 @@ +# +# Solution visibl +# +import spiceypy + +def visibl(): + # + # Local Parameters + # + METAKR = 'visibl.tm' + SCLKID = -82 + TDBFMT = 'YYYY MON DD HR:MN:SC.### TDB ::TDB' + MAXIVL = 1000 + MAXWIN = 2 * MAXIVL + # + # Load the meta-kernel. + # + spiceypy.furnsh( METAKR ) + # + # Assign the inputs for our search. + # + # Since we're interested in the apparent location of the + # target, we use light time and stellar aberration + # corrections. We use the "converged Newtonian" form + # of the light time correction because this choice may + # increase the accuracy of the occultation times we'll + # compute using gfoclt. + # + srfpt = 'DSS-14' + obsfrm = 'DSS-14_TOPO' + target = 'MEX' + abcorr = 'CN+S' + start = '2004 MAY 2 TDB' + stop = '2004 MAY 6 TDB' + elvlim = 6.0 + # + # The elevation limit above has units of degrees; we convert + # this value to radians for computation using SPICE routines. + # We'll store the equivalent value in radians in revlim. + # + revlim = spiceypy.rpd() * elvlim + # + # We model the target shape as a point. We either model the + # blocking body's shape as an ellipsoid, or we represent + # its shape using actual topographic data. No body-fixed + # reference frame is required for the target since its + # orientation is not used. + # + back = target + bshape = 'POINT' + bframe = ' ' + front = 'MARS' + fshape = 'ELLIPSOID' + fframe = 'IAU_MARS' + # + # The occultation type should be set to 'ANY' for a point + # target. + # + occtyp = 'any' + # + # Since SPICE doesn't directly support the AZ/EL coordinate + # system, we use the equivalent constraint + # + # latitude > revlim + # + # in the latitudinal coordinate system, where the reference + # frame is topocentric and is centered at the viewing location. + # + crdsys = 'LATITUDINAL' + coord = 'LATITUDE' + relate = '>' + # + # The adjustment value only applies to absolute extrema + # searches; simply give it an initial value of zero + # for this inequality search. + # + adjust = 0.0 + # + # stepsz is the step size, measured in seconds, used to search + # for times bracketing a state transition. Since we don't expect + # any events of interest to be shorter than five minutes, and + # since the separation between events is well over 5 minutes, + # we'll use this value as our step size. Units are seconds. + # + stepsz = 300.0 + # + # Display a banner for the output report: + # + print('\nInputs for target visibility search:\n') + print(f' Target = {target}') + print(f' Observation surface location = {srfpt}') + print(f" Observer's reference frame = {obsfrm}") + print(f' Blocking body = {front}') + print(f" Blocker's reference frame = {fframe}") + print(f' Elevation limit (degrees) = {elvlim:f}') + print(f' Aberration correction = {abcorr}') + print(f' Step size (seconds) = {stepsz:f}') + # + # Convert the start and stop times to ET. + # + etbeg = spiceypy.str2et( start ) + etend = spiceypy.str2et( stop ) + # + # Display the search interval start and stop times + # using the format shown below. + # + # 2004 MAY 06 20:15:00.000 (TDB) + # + btmstr = spiceypy.timout( etbeg, TDBFMT ) + print(f' Start time = {btmstr}') + etmstr = spiceypy.timout(etend, TDBFMT) + print(f' Stop time = {etmstr}') + + print( ' ' ) + # + # Initialize the "confinement" window with the interval + # over which we'll conduct the search. + # + cnfine = spiceypy.cell_double(2) + spiceypy.wninsd( etbeg, etend, cnfine ) + # + # In the call below, the maximum number of window + # intervals gfposc can store internally is set to MAXIVL. + # We set the cell size to MAXWIN to achieve this. + # + riswin = spiceypy.cell_double( MAXWIN ) + # + # Now search for the time period, within our confinement + # window, during which the apparent target has elevation + # at least equal to the elevation limit. + # + spiceypy.gfposc( target, obsfrm, abcorr, srfpt, + crdsys, coord, relate, revlim, + adjust, stepsz, MAXIVL, cnfine, riswin ) + # + # Now find the times when the apparent target is above + # the elevation limit and is not occulted by the + # blocking body (Mars). We'll find the window of times when + # the target is above the elevation limit and *is* occulted, + # then subtract that window from the view period window + # riswin found above. + # + # For this occultation search, we can use riswin as + # the confinement window because we're not interested in + # occultations that occur when the target is below the + # elevation limit. + # + # Find occultations within the view period window. + # + print( ' Searching using ellipsoid target shape model...' ) + eocwin = spiceypy.cell_double( MAXWIN ) + fshape = 'ELLIPSOID' + spiceypy.gfoclt( occtyp, front, fshape, fframe, + back, bshape, bframe, abcorr, + srfpt, stepsz, riswin, eocwin ) + print( ' Done.' ) + # + # Subtract the occultation window from the view period + # window: this yields the time periods when the target + # is visible. + # + evswin = spiceypy.wndifd( riswin, eocwin ) + # + # Repeat the search using low-resolution DSK data + # for the front body. + # + print( ' Searching using DSK target shape model...' ) + docwin = spiceypy.cell_double( MAXWIN ) + fshape = 'DSK/UNPRIORITIZED' + spiceypy.gfoclt( occtyp, front, fshape, fframe, + back, bshape, bframe, abcorr, + srfpt, stepsz, riswin, docwin ) + print( ' Done.\n' ) + dvswin = spiceypy.wndifd( riswin, docwin ) + # + # The function wncard returns the number of intervals + # in a SPICE window. + # + winsiz = spiceypy.wncard( evswin ) + if winsiz == 0: + print( 'No events were found.' ) + else: + # + # Display the visibility time periods. + # + print( + f'Visibility start and stop times of {target} as seen from {srfpt}\n' + 'using both ellipsoidal and DSK target shape models:\n' + ) + for i in range(winsiz): + # + # Fetch the start and stop times of + # the ith interval from the ellipsoid + # search result window evswin. + # + [intbeg, intend] = spiceypy.wnfetd( evswin, i ) + # + # Convert the rise time to TDB calendar strings. + # Write the results. + # + btmstr = spiceypy.timout( intbeg, TDBFMT ) + etmstr = spiceypy.timout( intend, TDBFMT ) + print(f' Ell: {btmstr} : {etmstr}') + # + # Fetch the start and stop times of + # the ith interval from the DSK + # search result window dvswin. + # + [dintbg, dinten] = spiceypy.wnfetd( dvswin, i ) + # + # Convert the rise time to TDB calendar strings. + # Write the results. + # + btmstr = spiceypy.timout( dintbg, TDBFMT ) + etmstr = spiceypy.timout( dinten, TDBFMT ) + print(f' DSK: {btmstr} : {etmstr}\n') + # + # End of result display loop. + # + spiceypy.unload( METAKR ) +if __name__ == '__main__': + visibl() \ No newline at end of file diff --git a/docs/scripts/event_finding/visibl_make_mk.py b/docs/scripts/event_finding/visibl_make_mk.py new file mode 100644 index 00000000..52499435 --- /dev/null +++ b/docs/scripts/event_finding/visibl_make_mk.py @@ -0,0 +1,37 @@ +mk = r""" +KPL/MK + Example meta-kernel for geometric event finding hands-on + coding lesson. + Version 3.0.0 26-OCT-2017 (BVS) + The names and contents of the kernels referenced by this + meta-kernel are as follows: + File Name Description + ------------------------------ ------------------------------ + de405xs.bsp Planetary ephemeris SPK, + subsetted to cover only + time range of interest. + earthstns_itrf93_050714.bsp DSN station SPK. + earth_topo_050714.tf DSN station frame definitions. + earth_000101_060525_060303.bpc Binary PCK for Earth. + naif0008.tls Generic LSK. + ORMM__040501000000_00076XS.BSP MEX Orbiter trajectory SPK, + subsetted to cover only + time range of interest. + pck00008.tpc Generic PCK. + mars_lowres.bds Low-resolution Mars DSK. +\begindata + KERNELS_TO_LOAD = ( + 'kernels/spk/de405xs.bsp' + 'kernels/spk/earthstns_itrf93_050714.bsp' + 'kernels/fk/earth_topo_050714.tf' + 'kernels/pck/earth_000101_060525_060303.bpc' + 'kernels/lsk/naif0008.tls' + 'kernels/spk/ORMM__040501000000_00076XS.BSP' + 'kernels/pck/pck00008.tpc' + 'kernels/dsk/mars_lowres.bds' + ) +\begintext +""" +with open("visibl.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file visibl.tm") diff --git a/docs/scripts/insitu_sensing/convrt.py b/docs/scripts/insitu_sensing/convrt.py new file mode 100644 index 00000000..068acfad --- /dev/null +++ b/docs/scripts/insitu_sensing/convrt.py @@ -0,0 +1,19 @@ +import spiceypy + + +def convrt(): + + mkfile = "convrt.tm" + spiceypy.furnsh(mkfile) + + utc = "2004-06-11T19:32:00" + et = spiceypy.str2et(utc) + + print(f"UTC = {utc}") + print(f"ET = {et:20.6f}") + + spiceypy.unload(mkfile) + + +if __name__ == "__main__": + convrt() diff --git a/docs/scripts/insitu_sensing/convrt_make_mk.py b/docs/scripts/insitu_sensing/convrt_make_mk.py new file mode 100644 index 00000000..374b9846 --- /dev/null +++ b/docs/scripts/insitu_sensing/convrt_make_mk.py @@ -0,0 +1,20 @@ +mk = r""" +KPL/MK + + The names and contents of the kernels referenced by this + meta-kernel are as follows: + + + File Name Description + -------------------------- ---------------------------------- + naif0008.tls Generic LSK. + +\begindata + KERNELS_TO_LOAD = ( + 'kernels/lsk/naif0008.tls' + ) +\begintext +""" +with open("convrt.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file convrt.tm") diff --git a/docs/scripts/insitu_sensing/getsta.py b/docs/scripts/insitu_sensing/getsta.py new file mode 100644 index 00000000..424498b5 --- /dev/null +++ b/docs/scripts/insitu_sensing/getsta.py @@ -0,0 +1,40 @@ +import spiceypy + + +def getsta(): + + mkfile = "getsta.tm" + spiceypy.furnsh(mkfile) + + utc = "2004-06-11T19:32:00" + et = spiceypy.str2et(utc) + + print(f"UTC = {utc:s}") + print(f"ET = {et:20.6f}") + + scid = -82 + sclk = "1465674964.105" + et = spiceypy.scs2e(scid, sclk) + + print(f"SCLK = {sclk:s}") + print(f"ET = {et:20.6f}") + + target = "CASSINI" + frame = "ECLIPJ2000" + corrtn = "NONE" + observ = "SUN" + + state, ltime = spiceypy.spkezr(target, et, frame, corrtn, observ) + + print(f" X = {state[0]:20.6f}") + print(f" Y = {state[1]:20.6f}") + print(f" Z = {state[2]:20.6f}") + print(f"VX = {state[3]:20.6f}") + print(f"VY = {state[4]:20.6f}") + print(f"VZ = {state[5]:20.6f}") + + spiceypy.unload(mkfile) + + +if __name__ == "__main__": + getsta() diff --git a/docs/scripts/insitu_sensing/getsta_make_mk.py b/docs/scripts/insitu_sensing/getsta_make_mk.py new file mode 100644 index 00000000..5432b062 --- /dev/null +++ b/docs/scripts/insitu_sensing/getsta_make_mk.py @@ -0,0 +1,30 @@ +mk = r""" +KPL/MK + + The names and contents of the kernels referenced by this + meta-kernel are as follows: + + + File Name Description + -------------------------- ---------------------------------- + naif0008.tls Generic LSK. + cas00084.tsc Cassini SCLK. + 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris SPK. + 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK. + 981005_PLTEPH-DE405S.bsp Planetary Ephemeris SPK. + sat128.bsp Saturnian Satellite Ephemeris SPK. + +\begindata + KERNELS_TO_LOAD = ( + 'kernels/lsk/naif0008.tls' + 'kernels/sclk/cas00084.tsc' + 'kernels/spk/020514_SE_SAT105.bsp' + 'kernels/spk/030201AP_SK_SM546_T45.bsp' + 'kernels/spk/981005_PLTEPH-DE405S.bsp' + 'kernels/spk/sat128.bsp' + ) +\begintext +""" +with open("getsta.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file getsta.tm") diff --git a/docs/scripts/insitu_sensing/sclket.py b/docs/scripts/insitu_sensing/sclket.py new file mode 100644 index 00000000..7b120054 --- /dev/null +++ b/docs/scripts/insitu_sensing/sclket.py @@ -0,0 +1,25 @@ +import spiceypy + +def sclket(): + + mkfile = 'sclket.tm' + spiceypy.furnsh(mkfile) + + utc = '2004-06-11T19:32:00' + et = spiceypy.str2et(utc) + + print(f'UTC = {utc:s}') + print(f'ET = {et:20.6f}') + + scid = -82 + sclk = '1465674964.105' + et = spiceypy.scs2e(scid, sclk) + + print(f'SCLK = {sclk:s}') + print(f'ET = {et:20.6f}') + + spiceypy.unload(mkfile) + + +if __name__ == '__main__': + sclket() diff --git a/docs/scripts/insitu_sensing/sclket_make_mk.py b/docs/scripts/insitu_sensing/sclket_make_mk.py new file mode 100644 index 00000000..095e2793 --- /dev/null +++ b/docs/scripts/insitu_sensing/sclket_make_mk.py @@ -0,0 +1,22 @@ +mk = r""" +KPL/MK + + The names and contents of the kernels referenced by this + meta-kernel are as follows: + + + File Name Description + -------------------------- ---------------------------------- + naif0008.tls Generic LSK. + cas00084.tsc Cassini SCLK. + +\begindata + KERNELS_TO_LOAD = ( + 'kernels/lsk/naif0008.tls' + 'kernels/sclk/cas00084.tsc' + ) +\begintext +""" +with open("sclket.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file sclket.tm") diff --git a/docs/scripts/insitu_sensing/scvel.py b/docs/scripts/insitu_sensing/scvel.py new file mode 100644 index 00000000..fa3d1b3e --- /dev/null +++ b/docs/scripts/insitu_sensing/scvel.py @@ -0,0 +1,95 @@ +import spiceypy + + +def scvel(): + + mkfile = "scvel.tm" + spiceypy.furnsh(mkfile) + + utc = "2004-06-11T19:32:00" + et = spiceypy.str2et(utc) + + print(f"UTC = {utc:s}") + print(f"ET = {et:20.6f}") + + scid = -82 + sclk = "1465674964.105" + et = spiceypy.scs2e(scid, sclk) + + print(f"SCLK = {sclk:s}") + print(f"ET = {et:20.6f}") + + target = "CASSINI" + frame = "ECLIPJ2000" + corrtn = "NONE" + observ = "SUN" + + state, ltime = spiceypy.spkezr(target, et, frame, corrtn, observ) + + print(f" X = {state[0]:20.6f}") + print(f" Y = {state[1]:20.6f}") + print(f" Z = {state[2]:20.6f}") + print(f"VX = {state[3]:20.6f}") + print(f"VY = {state[4]:20.6f}") + print(f"VZ = {state[5]:20.6f}") + + target = "SUN" + frame = "CASSINI_INMS" + corrtn = "LT+S" + observ = "CASSINI" + + sundir, ltime = spiceypy.spkpos(target, et, frame, corrtn, observ) + sundir = spiceypy.vhat(sundir) + + print(f"SUNDIR(X) = {sundir[0]:20.6f}") + print(f"SUNDIR(Y) = {sundir[1]:20.6f}") + print(f"SUNDIR(Z) = {sundir[2]:20.6f}") + + method = "NEAR POINT: ELLIPSOID" + target = "PHOEBE" + frame = "IAU_PHOEBE" + corrtn = "NONE" + observ = "CASSINI" + + spoint, trgepc, srfvec = spiceypy.subpnt(method, target, et, frame, corrtn, observ) + + srad, slon, slat = spiceypy.reclat(spoint) + + fromfr = "IAU_PHOEBE" + tofr = "CASSINI_INMS" + + m2imat = spiceypy.pxform(fromfr, tofr, et) + + sbpdir = spiceypy.mxv(m2imat, srfvec) + sbpdir = spiceypy.vhat(sbpdir) + + print(f"LON = {slon * spiceypy.dpr():20.6f}") + print(f"LAT = {slat * spiceypy.dpr():20.6f}") + print(f"SBPDIR(X) = {sbpdir[0]:20.6f}") + print(f"SBPDIR(Y) = {sbpdir[1]:20.6f}") + print(f"SBPDIR(Z) = {sbpdir[2]:20.6f}") + + target = "CASSINI" + frame = "J2000" + corrtn = "NONE" + observ = "PHOEBE" + + state, ltime = spiceypy.spkezr(target, et, frame, corrtn, observ) + scvdir = state[3:6] + + fromfr = "J2000" + tofr = "CASSINI_INMS" + j2imat = spiceypy.pxform(fromfr, tofr, et) + + scvdir = spiceypy.mxv(j2imat, scvdir) + scvdir = spiceypy.vhat(scvdir) + + print(f"SCVDIR(X) = {scvdir[0]:20.6f}") + print(f"SCVDIR(Y) = {scvdir[1]:20.6f}") + print(f"SCVDIR(Z) = {scvdir[2]:20.6f}") + + spiceypy.unload(mkfile) + + +if __name__ == "__main__": + scvel() diff --git a/docs/scripts/insitu_sensing/scvel_make_mk.py b/docs/scripts/insitu_sensing/scvel_make_mk.py new file mode 100644 index 00000000..ca5e9687 --- /dev/null +++ b/docs/scripts/insitu_sensing/scvel_make_mk.py @@ -0,0 +1,36 @@ +mk = r""" +KPL/MK + + The names and contents of the kernels referenced by this + meta-kernel are as follows: + + + File Name Description + -------------------------- ---------------------------------- + naif0008.tls Generic LSK. + cas00084.tsc Cassini SCLK. + 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris SPK. + 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK. + 981005_PLTEPH-DE405S.bsp Planetary Ephemeris SPK. + sat128.bsp Saturnian Satellite Ephemeris SPK. + 04135_04171pc_psiv2.bc Cassini Spacecraft CK. + cas_v37.tf Cassini FK. + cpck05Mar2004.tpc Cassini project PCK. + +\begindata + KERNELS_TO_LOAD = ( + 'kernels/lsk/naif0008.tls' + 'kernels/sclk/cas00084.tsc' + 'kernels/spk/020514_SE_SAT105.bsp' + 'kernels/spk/030201AP_SK_SM546_T45.bsp' + 'kernels/spk/981005_PLTEPH-DE405S.bsp' + 'kernels/spk/sat128.bsp' + 'kernels/ck/04135_04171pc_psiv2.bc' + 'kernels/fk/cas_v37.tf' + 'kernels/pck/cpck05Mar2004.tpc' + ) +\begintext +""" +with open("scvel.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file scvel.tm") diff --git a/docs/scripts/insitu_sensing/soldir.py b/docs/scripts/insitu_sensing/soldir.py new file mode 100644 index 00000000..d45a210e --- /dev/null +++ b/docs/scripts/insitu_sensing/soldir.py @@ -0,0 +1,53 @@ +import spiceypy + +def soldir(): + + mkfile = 'soldir.tm' + spiceypy.furnsh(mkfile) + + utc = '2004-06-11T19:32:00' + et = spiceypy.str2et(utc) + + print(f'UTC = {utc:s}') + print(f'ET = {et:20.6f}') + + scid = -82 + sclk = '1465674964.105' + et = spiceypy.scs2e(scid, sclk) + + print(f'SCLK = {sclk:s}') + print(f'ET = {et:20.6f}') + + target = 'CASSINI' + frame = 'ECLIPJ2000' + corrtn = 'NONE' + observ = 'SUN' + + state, ltime = spiceypy.spkezr(target, et, frame, + corrtn, observ) + + print(f' X = {state[0]:20.6f}') + print(f' Y = {state[1]:20.6f}') + print(f' Z = {state[2]:20.6f}') + print(f'VX = {state[3]:20.6f}') + print(f'VY = {state[4]:20.6f}') + print(f'VZ = {state[5]:20.6f}') + + target = 'SUN' + frame = 'CASSINI_INMS' + corrtn = 'LT+S' + observ = 'CASSINI' + + sundir, ltime = spiceypy.spkpos(target, et, frame, + corrtn, observ) + sundir = spiceypy.vhat(sundir) + + print(f'SUNDIR(X) = {sundir[0]:20.6f}') + print(f'SUNDIR(Y) = {sundir[1]:20.6f}') + print(f'SUNDIR(Z) = {sundir[2]:20.6f}') + + spiceypy.unload(mkfile) + + +if __name__ == '__main__': + soldir() diff --git a/docs/scripts/insitu_sensing/soldir_make_mk.py b/docs/scripts/insitu_sensing/soldir_make_mk.py new file mode 100644 index 00000000..7d188b39 --- /dev/null +++ b/docs/scripts/insitu_sensing/soldir_make_mk.py @@ -0,0 +1,34 @@ +mk = r""" +KPL/MK + + The names and contents of the kernels referenced by this + meta-kernel are as follows: + + + File Name Description + -------------------------- ---------------------------------- + naif0008.tls Generic LSK. + cas00084.tsc Cassini SCLK. + 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris SPK. + 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK. + 981005_PLTEPH-DE405S.bsp Planetary Ephemeris SPK. + sat128.bsp Saturnian Satellite Ephemeris SPK. + 04135_04171pc_psiv2.bc Cassini Spacecraft CK. + cas_v37.tf Cassini FK. + +\begindata + KERNELS_TO_LOAD = ( + 'kernels/lsk/naif0008.tls' + 'kernels/sclk/cas00084.tsc' + 'kernels/spk/020514_SE_SAT105.bsp' + 'kernels/spk/030201AP_SK_SM546_T45.bsp' + 'kernels/spk/981005_PLTEPH-DE405S.bsp' + 'kernels/spk/sat128.bsp' + 'kernels/ck/04135_04171pc_psiv2.bc' + 'kernels/fk/cas_v37.tf' + ) +\begintext +""" +with open("soldir.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file soldir.tm") diff --git a/docs/scripts/insitu_sensing/sscpnt.py b/docs/scripts/insitu_sensing/sscpnt.py new file mode 100644 index 00000000..3b27b078 --- /dev/null +++ b/docs/scripts/insitu_sensing/sscpnt.py @@ -0,0 +1,76 @@ +import spiceypy + + +def sscpnt(): + + mkfile = "sscpnt.tm" + spiceypy.furnsh(mkfile) + + utc = "2004-06-11T19:32:00" + et = spiceypy.str2et(utc) + + print(f"UTC = {utc:s}") + print(f"ET = {et:20.6f}") + + scid = -82 + sclk = "1465674964.105" + et = spiceypy.scs2e(scid, sclk) + + print(f"SCLK = {sclk:s}") + print(f"ET = {et:20.6f}") + + target = "CASSINI" + frame = "ECLIPJ2000" + corrtn = "NONE" + observ = "SUN" + + state, ltime = spiceypy.spkezr(target, et, frame, corrtn, observ) + + print(f" X = {state[0]:20.6f}") + print(f" Y = {state[1]:20.6f}") + print(f" Z = {state[2]:20.6f}") + print(f"VX = {state[3]:20.6f}") + print(f"VY = {state[4]:20.6f}") + print(f"VZ = {state[5]:20.6f}") + + target = "SUN" + frame = "CASSINI_INMS" + corrtn = "LT+S" + observ = "CASSINI" + + sundir, ltime = spiceypy.spkpos(target, et, frame, corrtn, observ) + sundir = spiceypy.vhat(sundir) + + print(f"SUNDIR(X) = {sundir[0]:20.6f}") + print(f"SUNDIR(Y) = {sundir[1]:20.6f}") + print(f"SUNDIR(Z) = {sundir[2]:20.6f}") + + method = "NEAR POINT: ELLIPSOID" + target = "PHOEBE" + frame = "IAU_PHOEBE" + corrtn = "NONE" + observ = "CASSINI" + + spoint, trgepc, srfvec = spiceypy.subpnt(method, target, et, frame, corrtn, observ) + + srad, slon, slat = spiceypy.reclat(spoint) + + fromfr = "IAU_PHOEBE" + tofr = "CASSINI_INMS" + + m2imat = spiceypy.pxform(fromfr, tofr, et) + + sbpdir = spiceypy.mxv(m2imat, srfvec) + sbpdir = spiceypy.vhat(sbpdir) + + print(f"LON = {slon * spiceypy.dpr():20.6f}") + print(f"LAT = {slat * spiceypy.dpr():20.6f}") + print(f"SBPDIR(X) = {sbpdir[0]:20.6f}") + print(f"SBPDIR(Y) = {sbpdir[1]:20.6f}") + print(f"SBPDIR(Z) = {sbpdir[2]:20.6f}") + + spiceypy.unload(mkfile) + + +if __name__ == "__main__": + sscpnt() diff --git a/docs/scripts/insitu_sensing/sscpnt_make_mk.py b/docs/scripts/insitu_sensing/sscpnt_make_mk.py new file mode 100644 index 00000000..738d676e --- /dev/null +++ b/docs/scripts/insitu_sensing/sscpnt_make_mk.py @@ -0,0 +1,36 @@ +mk = r""" +KPL/MK + + The names and contents of the kernels referenced by this + meta-kernel are as follows: + + + File Name Description + -------------------------- ---------------------------------- + naif0008.tls Generic LSK. + cas00084.tsc Cassini SCLK. + 020514_SE_SAT105.bsp Saturnian Satellite Ephemeris SPK. + 030201AP_SK_SM546_T45.bsp Cassini Spacecraft SPK. + 981005_PLTEPH-DE405S.bsp Planetary Ephemeris SPK. + sat128.bsp Saturnian Satellite Ephemeris SPK. + 04135_04171pc_psiv2.bc Cassini Spacecraft CK. + cas_v37.tf Cassini FK. + cpck05Mar2004.tpc Cassini project PCK. + +\begindata + KERNELS_TO_LOAD = ( + 'kernels/lsk/naif0008.tls' + 'kernels/sclk/cas00084.tsc' + 'kernels/spk/020514_SE_SAT105.bsp' + 'kernels/spk/030201AP_SK_SM546_T45.bsp' + 'kernels/spk/981005_PLTEPH-DE405S.bsp' + 'kernels/spk/sat128.bsp' + 'kernels/ck/04135_04171pc_psiv2.bc' + 'kernels/fk/cas_v37.tf' + 'kernels/pck/cpck05Mar2004.tpc' + ) +\begintext +""" +with open("sscpnt.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file sscpnt.tm") diff --git a/docs/scripts/other_stuff/aderr.py b/docs/scripts/other_stuff/aderr.py new file mode 100644 index 00000000..c494fd34 --- /dev/null +++ b/docs/scripts/other_stuff/aderr.py @@ -0,0 +1,60 @@ +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") diff --git a/docs/scripts/other_stuff/coord.py b/docs/scripts/other_stuff/coord.py new file mode 100644 index 00000000..b74d18e3 --- /dev/null +++ b/docs/scripts/other_stuff/coord.py @@ -0,0 +1,134 @@ +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) diff --git a/docs/scripts/other_stuff/kervar.py b/docs/scripts/other_stuff/kervar.py new file mode 100644 index 00000000..6ea17839 --- /dev/null +++ b/docs/scripts/other_stuff/kervar.py @@ -0,0 +1,106 @@ +# +# Import the CSPICE-Python interface. +# +import spiceypy + + +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 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) + 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) + print("EXAMPLE_TIMES") + for dvar in dvars: + print(" Time value: {0:20.6f}".format(dvar)) + # + # Done. Unload the kernels. + # + spiceypy.kclear() + + +if __name__ == "__main__": + kervar() diff --git a/docs/scripts/other_stuff/kervar_make_mk.py b/docs/scripts/other_stuff/kervar_make_mk.py new file mode 100644 index 00000000..33a62eef --- /dev/null +++ b/docs/scripts/other_stuff/kervar_make_mk.py @@ -0,0 +1,46 @@ +mk = r""" + KPL/MK + Name the kernels to load. Use path symbols. + The names and contents of the kernels referenced by this + meta-kernel are as follows: + File Name Description + --------------- ------------------------------ + naif0008.tls Generic LSK. + de405s.bsp Planet Ephemeris SPK. + pck00008.tpc Generic PCK. + \begindata + PATH_VALUES = ('kernels/spk', + 'kernels/pck', + 'kernels/lsk') + PATH_SYMBOLS = ('SPK' , 'PCK' , 'LSK' ) + KERNELS_TO_LOAD = ( '$SPK/de405s.bsp', + '$PCK/pck00008.tpc', + '$LSK/naif0008.tls') + \begintext + Ring model data. + \begindata + BODY699_RING1_NAME = 'A Ring' + BODY699_RING1 = (122170.0 136780.0 0.1 0.1 0.5) + BODY699_RING1_1_NAME = 'Encke Gap' + BODY699_RING1_1 = (133405.0 133730.0 0.0 0.0 0.0) + BODY699_RING2_NAME = 'Cassini Division' + BODY699_RING2 = (117580.0 122170.0 0.0 0.0 0.0) + \begintext + The kernel pool recognizes values preceded by '@' as time + values. When read, the kernel subsystem converts these + representations into double precision ephemeris time. + Caution: The kernel subsystem interprets the time strings + identified by '@' as TDB. The same string passed as input + to @STR2ET is processed as UTC. + The three expressions stored in the EXAMPLE_TIMES array represent + the same epoch. + \begindata + EXAMPLE_TIMES = ( @APRIL-1-2004-12:34:56.789, + @4/1/2004-12:34:56.789, + @JD2453097.0242684 + ) + \begintext +""" +with open("kervar.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file kervar.tm") diff --git a/docs/scripts/other_stuff/kpool.py b/docs/scripts/other_stuff/kpool.py new file mode 100644 index 00000000..521c1746 --- /dev/null +++ b/docs/scripts/other_stuff/kpool.py @@ -0,0 +1,56 @@ +# +# 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") diff --git a/docs/scripts/other_stuff/kpool_generic_make_mk.py b/docs/scripts/other_stuff/kpool_generic_make_mk.py new file mode 100644 index 00000000..daca4641 --- /dev/null +++ b/docs/scripts/other_stuff/kpool_generic_make_mk.py @@ -0,0 +1,26 @@ +mk = r""" +KPL/MK + Define the paths to the kernel directory. Use the PATH_SYMBOLS + as aliases to the paths. + The names and contents of the kernels referenced by this + meta-kernel are as follows: + File Name Description + --------------- ------------------------------ + naif0008.tls Generic LSK. + de405s.bsp Planet Ephemeris SPK. + pck00008.tpc Generic PCK. +\begindata + PATH_VALUES = ( 'kernels/lsk', + 'kernels/spk', + 'kernels/pck' ) + + PATH_SYMBOLS = ( 'LSK', 'SPK', 'PCK' ) + + KERNELS_TO_LOAD = ( '$LSK/naif0008.tls', + '$SPK/de405s.bsp', + '$PCK/pck00008.tpc' ) +\begintext +""" +with open("kpool_generic.tm", "w") as dst: + dst.write(mk) +print("Wrote kernel file kpool_generic.tm") diff --git a/docs/scripts/other_stuff/kpool_make_mk.py b/docs/scripts/other_stuff/kpool_make_mk.py new file mode 100644 index 00000000..5a06a8ec --- /dev/null +++ b/docs/scripts/other_stuff/kpool_make_mk.py @@ -0,0 +1,11 @@ +mk = r""" +KPL/MK +\begindata +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") diff --git a/docs/scripts/other_stuff/units.py b/docs/scripts/other_stuff/units.py new file mode 100644 index 00000000..bf84897b --- /dev/null +++ b/docs/scripts/other_stuff/units.py @@ -0,0 +1,51 @@ +# +# 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") diff --git a/docs/scripts/other_stuff/win.py b/docs/scripts/other_stuff/win.py new file mode 100644 index 00000000..122ac586 --- /dev/null +++ b/docs/scripts/other_stuff/win.py @@ -0,0 +1,148 @@ +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() diff --git a/docs/scripts/other_stuff/xconst.py b/docs/scripts/other_stuff/xconst.py new file mode 100644 index 00000000..40d3518d --- /dev/null +++ b/docs/scripts/other_stuff/xconst.py @@ -0,0 +1,82 @@ +# +# Import the CSPICE-Python interface. +# +import spiceypy + + +def xconst(): + # + # All the function have the same calling sequence: + # + # VALUE = function_name() + # + # some_procedure( function_name() ) + # + # First a simple example using the seconds per day + # constant... + # + print("Number of (S)econds (P)er (D)ay : {0:19.12f}".format(spiceypy.spd())) + + # + # ...then show the value of degrees per radian, 180/Pi... + # + print("Number of (D)egrees (P)er (R)adian: {0:19.16f}".format(spiceypy.dpr())) + + # + # ...and the inverse, radians per degree, Pi/180. + # It is obvious spiceypy.dpr() equals 1.d/spiceypy.rpd(), or + # more simply spiceypy.dpr() * spiceypy.rpd() equals 1 + # + print("Number of (R)adians (P)er (D)egree: {0:19.16f}".format(spiceypy.rpd())) + + # + # What's the value for the astrophysicist's favorite + # physical constant (in a vacuum)? + # + print("Speed of light in KM per second : {0:19.12f}".format(spiceypy.clight())) + + # + # How long (in Julian days) from the J2000 epoch to the + # J2100 epoch? + # + print("Number of days between epochs J2000") + print(" and J2100 : {0:19.12f}".format(spiceypy.j2100() - spiceypy.j2000())) + + # + # Redo the calculation returning seconds... + # + print("Number of seconds between epochs") + print( + " J2000 and J2100 : {0:19.5f}".format( + spiceypy.spd() * (spiceypy.j2100() - spiceypy.j2000()) + ) + ) + + # + # ...then tropical years. + # + val = (spiceypy.spd() / spiceypy.tyear()) * (spiceypy.j2100() - spiceypy.j2000()) + print("Number of tropical years between") + print(" epochs J2000 and J2100 : {0:19.12f}".format(val)) + + # + # Finally, how can I convert a radian value to degrees. + # + print( + "Number of degrees in Pi/2 radians of arc: {0:19.16f}".format( + spiceypy.halfpi() * spiceypy.dpr() + ) + ) + + # + # and degrees to radians. + # + print( + "Number of radians in 250 degrees of arc : {0:19.16f}".format( + 250.0 * spiceypy.rpd() + ) + ) + + +if __name__ == "__main__": + xconst() diff --git a/docs/scripts/other_stuff/xtic.py b/docs/scripts/other_stuff/xtic.py new file mode 100644 index 00000000..3754b0d3 --- /dev/null +++ b/docs/scripts/other_stuff/xtic.py @@ -0,0 +1,106 @@ +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() diff --git a/docs/scripts/remote_sensing/convtm.py b/docs/scripts/remote_sensing/convtm.py new file mode 100644 index 00000000..f8119b1d --- /dev/null +++ b/docs/scripts/remote_sensing/convtm.py @@ -0,0 +1,32 @@ +# Solution convtm +import spiceypy + +def convtm(utctim='2004 jun 11 19:32:00'): + # Local Parameters + METAKR = 'convtm.tm' + SCLKID = -82 + spiceypy.furnsh(METAKR) + + print(f'Converting UTC Time: {utctim}') + # Convert utctim to ET. + 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}') + + # unload the kernel + spiceypy.unload(METAKR) + +# provide the input UTC Time: +convtm('2004 jun 11 19:32:00') diff --git a/docs/scripts/remote_sensing/convtm_make_mk.py b/docs/scripts/remote_sensing/convtm_make_mk.py new file mode 100644 index 00000000..8b0d8624 --- /dev/null +++ b/docs/scripts/remote_sensing/convtm_make_mk.py @@ -0,0 +1,23 @@ +mk = r""" +KPL/MK + + This is the meta-kernel used in the solution of the "Time + Conversion" 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 + + + \begindata + 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') diff --git a/docs/scripts/remote_sensing/fovint.py b/docs/scripts/remote_sensing/fovint.py new file mode 100644 index 00000000..85c4ae96 --- /dev/null +++ b/docs/scripts/remote_sensing/fovint.py @@ -0,0 +1,97 @@ +# +# 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() diff --git a/docs/scripts/remote_sensing/fovint_make_mk.py b/docs/scripts/remote_sensing/fovint_make_mk.py new file mode 100644 index 00000000..94b96096 --- /dev/null +++ b/docs/scripts/remote_sensing/fovint_make_mk.py @@ -0,0 +1,40 @@ +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') diff --git a/docs/scripts/remote_sensing/getsta.py b/docs/scripts/remote_sensing/getsta.py new file mode 100644 index 00000000..a6ab9a30 --- /dev/null +++ b/docs/scripts/remote_sensing/getsta.py @@ -0,0 +1,66 @@ +# Solution getsta.py +import spiceypy + +def getsta(utctim='2004 jun 11 19:32:00'): + # 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) + + print(f'Converting UTC Time: {utctim}') + # Convert utctim to ET. + 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 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(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) + +# provide the input UTC Time: +getsta('2004 jun 11 19:32:00') diff --git a/docs/scripts/remote_sensing/getsta_make_mk.py b/docs/scripts/remote_sensing/getsta_make_mk.py new file mode 100644 index 00000000..24df7a66 --- /dev/null +++ b/docs/scripts/remote_sensing/getsta_make_mk.py @@ -0,0 +1,28 @@ +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. + + 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 + + + \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') diff --git a/docs/scripts/remote_sensing/subpts.py b/docs/scripts/remote_sensing/subpts.py new file mode 100644 index 00000000..969ee305 --- /dev/null +++ b/docs/scripts/remote_sensing/subpts.py @@ -0,0 +1,31 @@ +# 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() diff --git a/docs/scripts/remote_sensing/subpts_make_mk.py b/docs/scripts/remote_sensing/subpts_make_mk.py new file mode 100644 index 00000000..c36cdd78 --- /dev/null +++ b/docs/scripts/remote_sensing/subpts_make_mk.py @@ -0,0 +1,33 @@ +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') diff --git a/docs/scripts/remote_sensing/xform.py b/docs/scripts/remote_sensing/xform.py new file mode 100644 index 00000000..9aa1f489 --- /dev/null +++ b/docs/scripts/remote_sensing/xform.py @@ -0,0 +1,72 @@ +# +# 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() diff --git a/docs/scripts/remote_sensing/xform_make_mk.py b/docs/scripts/remote_sensing/xform_make_mk.py new file mode 100644 index 00000000..bbae760d --- /dev/null +++ b/docs/scripts/remote_sensing/xform_make_mk.py @@ -0,0 +1,36 @@ +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 + 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 + + + \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' ) + \begintext +""" +with open('xform.tm', 'w') as dst: + dst.write(mk) +print('Wrote kernel file xform.tm') diff --git a/src/spiceypy/spiceypy.py b/src/spiceypy/spiceypy.py index 56c3b411..4807490e 100644 --- a/src/spiceypy/spiceypy.py +++ b/src/spiceypy/spiceypy.py @@ -16138,7 +16138,7 @@ def vprjp(vin: Union[ndarray, Iterable[float]], plane: Plane) -> ndarray: https://naif.jpl.nasa.gov/pub/naif/misc/toolkit_docs_N0067/C/cspice/vprjp_c.html :param vin: Vector to be projected. - :param plane: A Plane onto which `vin' is projected. + :param plane: A Plane onto which vin is projected. :return: Vector resulting from projection. """ vin = stypes.to_double_vector(vin)