# Remote Sensing Hands-On Lesson using BepiColombo MPO¶

Virtual SPICE Training for BepiColombo, July 21-22, 2020

## Overview¶

In this lesson you will develop a series of simple programs that demonstrate the usage of SpiceyPy to compute a variety of different geometric quantities applicable to experiments carried out by a remote sensing instrument flown on an interplanetary spacecraft.

You may find it useful to consult the permuted index, the headers of various source modules, and several Required Reading documents available at the NAIF site.

## Initialise SPICE by importing SpiceyPy¶

For the following exercises, instead of loading the meta-kernel try to sort out the exact kernels that you need to load for the given execrcise and load them (unless indicated).

In [2]:

    import spiceypy

## Time Conversion¶

Write a program that given a UTC time string, converts it to the following time systems and output formats:

- Ephemeris Time (ET) in seconds past J2000
- Calendar Ephemeris Time
- Spacecraft Clock Time (TIP: You will need to know the NAIF ID of MPO)

and displays the results. Use the program to convert "2020-OCT-15 03:57:49" UTC into these alternate systems.

In [3]:

    #
    # We need to load the leapseconds kernel and the SCLK kernel.
    #
    spiceypy.furnsh('../kernels/lsk/naif0012.tls')
    spiceypy.furnsh('../kernels/sclk/bc_mpo_step_20200713.tsc')

    et = spiceypy.utc2et('2020-10-15T03:57:49')
    print('   Ephemeris Time (ET) in seconds past J2000: {}'.format(et))

    calet = spiceypy.timout( et, 'YYYY-MON-DDTHR:MN:SC ::TDB' )
    print( '   Calendar Ephemeris Time:                   {:s}'.format( calet ) )

    #
    # We will need the SCLK ID of MPO that we can retrieve from the SCLK file itself.
    #
    sclkid = -121

    sclkst = spiceypy.sce2s( sclkid, et )
    print( '   Spacecraft Clock Time:                     {:s}'.format( sclkst ) )

    #
    # We unload all the kernels in the kernel pool
    #
    spiceypy.kclear()

       Ephemeris Time (ET) in seconds past J2000: 656006338.1823678
       Calendar Ephemeris Time:                   2020-OCT-15T03:58:58
       Spacecraft Clock Time:                     1/0667454267:48104

## Obtaining Target States and Positions - Venus Flyby¶

Write a program that given a UTC time string computes the following quantities at that epoch:

- The apparent state of Venus as seen from BepiColombo MPO 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 one-way light time between BepiColombo MPO and the apparent position of Earth, in seconds.

- The actual (geometric) distance between the Sun and Venus, in astronomical units.

and displays the results. Use the program to compute these quantities at "2020-OCT-15 03:57:49" UTC.

In [4]:

    #
    # We need to load the leapseconds kernel the Solar System ephemeris and the MPO SPK.
    #
    spiceypy.furnsh('../kernels/lsk/naif0012.tls')
    spiceypy.furnsh('../kernels/spk/de432s_20201013_20201016.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mpo_fcp_Venus1SwingbyMTP_v01.bsp')

    et = spiceypy.utc2et('2020-10-15T03:57:49')

    #
    # Compute the apparent state of Mercury as seen from BepiColombo MPO in the J2000 frame.
    # All of the ephemeris readers return states in units of kilometers and km/s.
    #
    [state, ltime] = spiceypy.spkezr('VENUS', et,'J2000','LT+S','MPO' )

    print( ' Apparent state of Venus as seen from BepiColombo MPO 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}\n'.format(state[5])     )

    #
    # Compute the apparent position of Earth as seen from
    # BepiColombo MPO in the J2000 frame.
    #
    [pos, ltime] = spiceypy.spkpos( 'EARTH', et, 'J2000', 'LT+S', 'MPO')

    print( ' One way light time between BepiColombo MPO and the apparent\n'
           ' position of Earth: {} seconds\n'.format(ltime))

    #
    # Now we need to compute the actual distance between the Sun and Mercury.
    # We need to adjust our aberration correction appropriately.
    #
    [pos, ltime] = spiceypy.spkpos( 'SUN',  et, 'J2000','NONE', 'VENUS')

    #
    # 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 Venus body centers: {} AU'.format(dist))

    spiceypy.kclear()

     Apparent state of Venus as seen from BepiColombo MPO in the J2000
     frame (km, km/s):
          X =        14496.540
          Y =          786.114
          Z =         8286.990
         VX =            2.258
         VY =           -9.366
         VZ =           -3.061

     One way light time between BepiColombo MPO and the apparent
     position of Earth: 582.6527711004886 seconds

     Actual distance between Sun and Venus body centers: 0.7188927953509128 AU

## Spacecraft Orientation and Reference Frames¶

Write a program that given a UTC time string computes and displays the following at the epoch of interest:

- The angular separation between the apparent position of BepiColombo MMO as seen from BepiColombo MPO and the nominal instrument view direction.

The nominal instrument view direction is not provided by any kernel variable, but it is indicated in the BepiColombo MPO frame kernel.

Use the program to compute these quantities at the epoch "2027 JUN 11 20:50:00" UTC.

In [6]:

    #
    # Since we need orientation information in addition to the kernels we loaded before
    # now we need to load the Frames Kernel, the SCLK kernel and the MPO CK kernel.
    # It is also important to note that we need to load the frames kernel containing the
    # MPO Science frames
    #
    spiceypy.furnsh('../kernels/lsk/naif0012.tls')
    spiceypy.furnsh('../kernels/spk/de432s_20270609_20270614.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mpo_mlt_50037_20270609_20270614_v01.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mmo_mlt_50038_20270609_20270614_v01.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mpo_cog_v01.bsp')
    spiceypy.furnsh('../kernels/sclk/bc_mpo_step_20200713.tsc')
    spiceypy.furnsh('../kernels/ck/bc_mpo_sc_slt_50028_20270609_20270614_s20200713_v01.bc')
    spiceypy.furnsh('../kernels/fk/bc_mpo_v23.tf')
    spiceypy.furnsh('../kernels/fk/bc_sci_v06.tf')

    et = spiceypy.utc2et('2027-06-11T20:50:00')

    #
    # We compute the apparent position of MMO as seen from
    # MPO in the J2000 frame.
    #
    [pos, ltime] = spiceypy.spkpos('MMO',et,'J2000','LT+S','MPO')
    [pos, ltime] = spiceypy.spkpos('MMO',et,'MPO_SPACECRAFT','LT+S','MPO')

    #
    # Now compute the location of the nominal instrument view
    # direction.  From reading the frame kernel we know that
    # the instrument view direction is nominally the +Z axis
    # of the TGO_SPACECRAFT frame defined there.
    #
    bsight = [ 0.0, 0.0, 1.0]

    #
    # Now compute the rotation matrix from MPO_SPACERAFT into
    # J2000.
    #
    pform = spiceypy.pxform('MPO_SPACECRAFT','J2000', et )

    #
    # And multiply the result to obtain the nominal instrument
    # view direction 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 MMO and the\n'
          ' MPO nominal instrument view direction (degrees): {:.3f}'.format(sep))

    spiceypy.kclear()

     Angular separation between the apparent position of MMO and the
     MPO nominal instrument view direction (degrees): 79.750

## Computing Sub-s/c and Sub-solar Points on an Ellipsoid¶

Write a program that given a UTC time string computes the following quantities at that epoch:

- The apparent sub-observer point of MPO on Mercury, in the body fixed frame IAU_MERCURY, in kilometers.
- The apparent sub-solar point on Mercury, as seen from MPO in the body fixed frame IAU_MERCURY, in kilometers.

For the computations use the ellipsoidal shape model:

    near point/ellipsoid

definition.

The program displays the results. Use the program to compute these quantities at "2027 JUN 11 20:10:00" UTC.

In [7]:

    spiceypy.furnsh('../kernels/lsk/naif0012.tls')
    spiceypy.furnsh('../kernels/spk/de432s_20270609_20270614.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mpo_mlt_50037_20270609_20270614_v01.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mmo_mlt_50038_20270609_20270614_v01.bsp')
    spiceypy.furnsh('../kernels/spk/bc_mpo_cog_v01.bsp')
    spiceypy.furnsh('../kernels/sclk/bc_mpo_step_20200713.tsc')
    spiceypy.furnsh('../kernels/ck/bc_mpo_sc_slt_50028_20270609_20270614_s20200713_v01.bc')
    spiceypy.furnsh('../kernels/fk/bc_mpo_v23.tf')
    spiceypy.furnsh('../kernels/fk/bc_sci_v06.tf')
    spiceypy.furnsh('../kernels/pck/pck00010.tpc')

    et = spiceypy.utc2et('2027-06-11T20:10:00')

    #
    # Use the "near point" sub-point definition
    # and an ellipsoidal model.
    #
    method = 'NEAR POINT/Ellipsoid'

    #
    # Compute the apparent sub-observer point of MPO on Mercury.
    #
    [spoint, trgepc, srfvec] = spiceypy.subpnt(method,'MERCURY',et,
                                               'IAU_MERCURY','LT+S','MPO' )

    print( ' Apparent sub-observer point of MPO on Mercury\n'
           ' in the IAU_MERCURY frame (km):' )
    print( '      X = {:.3f}'.format(spoint[0])              )
    print( '      Y = {:.3f}'.format(spoint[1])              )
    print( '      Z = {:.3f}'.format(spoint[2])              )
    print( '    ALT = {:.3f}\n'.format(spiceypy.vnorm(srfvec)) )

    #
    # Compute the apparent sub-solar point on Mercury as seen from MPO
    #
    [spoint, trgepc, srfvec] = spiceypy.subslr(method,'MERCURY',et,'IAU_MERCURY',
                                               'LT+S','MPO' )

    print( ' Apparent sub-solar point on Mercury as seen from MPO \n'
           ' in the IAU_MERCURY frame (km):'  )
    print( '      X = {:.3f}'.format(spoint[0])   )
    print( '      Y = {:.3f}'.format(spoint[1])   )
    print( '      Z = {:.3f}\n'.format(spoint[2])   )

    #
    # We unload all the kernels in the kernel pool
    #
    spiceypy.kclear()

     Apparent sub-observer point of MPO on Mercury
     in the IAU_MERCURY frame (km):
          X = -1491.318
          Y = 1817.025
          Z = -653.089
        ALT = 307.635

     Apparent sub-solar point on Mercury as seen from MPO
     in the IAU_MERCURY frame (km):
          X = -908.060
          Y = 2264.412
          Z = -0.801

## Intersecting Vectors with an Ellipsoid (fovint)¶

Write a program given an input UTC time string that computes the intersection of the MPO SERENA ELENA boresight and field of view (FOV) boundary vectors with the surface of Mercury.
The program presents each point of intersection as

- Planetocentric (latitudinal) coordinates in the IAU_MERCURY frame.

For each of the sensor FOV boundary and boresight vectors, if an intersection is found, the program displays the results of the above computations, otherwise it indicates no intersection exists.

At each point of intersection compute the following:

- Phase angle
- Solar incidence angle
- Emission angle

Use this program to compute values at "2027 JUN 11 20:10:00" UTC.

In [10]:

    spiceypy.furnsh('../kernels/mk/bc_training_class.tm')

    et = spiceypy.utc2et('2027-06-11T20:10:00')

    #
    # Now we need to obtain the FOV configuration of
    # the SERENA ELENA aperture.  To do this we will
    # need the ID code for MPO_SERENA_ELENA.
    #
    lnonid = spiceypy.bodn2c('MPO_SERENA_ELENA')

    #
    # Now retrieve the field of view parameters.
    #
    [shape,insfrm,bsight,n,bounds ] = spiceypy.getfov(lnonid,4)

    #
    # `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',
               'MPO_SERENA_ELENA Boresight' ]

    #
    # Now perform the same set of calculations for each
    # vector listed in the BOUNDS array.
    #
    for i  in  range(5):
        #
        # Call sincpt to determine coordinates of the
        # intersection of this vector with the surface
        # of Mercury.
        #
        print( ' Vector: {:s}\n'.format( vecnam[i] ) )

        [point,trgepc,srfvec] = spiceypy.sincpt('ELLIPSOID','MERCURY',et,
                                                'IAU_MERCURY', 'LT+S','MPO',
                                                insfrm, bounds[i])

        #
        # Display the planetocentric latitude and longitude of the intercept.
        #
        [radius,lon,lat] = spiceypy.reclat( point )

        print( ' Planetocentric coordinates of the intercept (degrees):')
        print( '     LAT = {:.3f}'.format(lat * spiceypy.dpr() )  )
        print( '     LON = {:.3f}\n'.format(lon * spiceypy.dpr() )  )

        #
        # Compute the illumination angles at this point.
        #
        [trgepc,srfvec,phase,solar,emissn, visibl,lit] = spiceypy.illumf(
                 'ELLIPSOID','MERCURY','SUN',et,'IAU_MERCURY',
                 'LT+S','MPO', point)

        print( '  Phase angle (degrees): {:.3f}'.format(phase*spiceypy.dpr()))
        print( '  Solar incidence angle (degrees): {:.3f}'.format(solar*spiceypy.dpr()))
        print( '  Emission angle (degrees): {:.3f}'.format(emissn*spiceypy.dpr()))
        print( '  Observer visible:  {:s}'.format(str(visibl)))
        print( '  Sun visible:       {:s}\n'.format(str(lit)))

    #
    # End of vector loop.
    #

    spiceypy.kclear()

     Vector: Boundary Corner 1

     Planetocentric coordinates of the intercept (degrees):
         LAT = -15.956
         LON = 120.441

      Phase angle (degrees): 65.596
      Solar incidence angle (degrees): 18.052
      Emission angle (degrees): 56.075
      Observer visible:  True
      Sun visible:       True

     Vector: Boundary Corner 2

     Planetocentric coordinates of the intercept (degrees):
         LAT = -14.645
         LON = 120.501

      Phase angle (degrees): 67.390
      Solar incidence angle (degrees): 16.944
      Emission angle (degrees): 56.091
      Observer visible:  True
      Sun visible:       True

     Vector: Boundary Corner 3

     Planetocentric coordinates of the intercept (degrees):
         LAT = -14.875
         LON = 133.871

      Phase angle (degrees): 22.403
      Solar incidence angle (degrees): 26.354
      Emission angle (degrees): 35.037
      Observer visible:  True
      Sun visible:       True

     Vector: Boundary Corner 4

     Planetocentric coordinates of the intercept (degrees):
         LAT = -16.107
         LON = 133.891

      Phase angle (degrees): 16.039
      Solar incidence angle (degrees): 27.048
      Emission angle (degrees): 35.012
      Observer visible:  True
      Sun visible:       True

     Vector: MPO_SERENA_ELENA Boresight

     Planetocentric coordinates of the intercept (degrees):
         LAT = -15.517
         LON = 129.377

      Phase angle (degrees): 23.289
      Solar incidence angle (degrees): 23.234
      Emission angle (degrees): 0.085
      Observer visible:  True
      Sun visible:       True

## Extra Credit: Lessons with WGC¶

Try to reproduce all the previous calculations with WebGeocalc. You will need to load the appropriate meta-kernel: "SPICE CLASS -- BepiColombo Remote Sensing Lesson Kernels"