Instrument field of views#

In the example below, we will work on the 2nd flyby of Ganymede (2G2) in February 2032 seen by the JANUS instrument with a time step of 10 secondes around closest approach:

Tip

You can omit the time parts like T00:00:00, :00:00 or :00.

janus_flyby = tour['2032-02-13T22':'2032-02-14':'10 sec'].new_traj(instrument='JANUS')

janus_flyby
<InstrumentTrajectory> Observer: JUICE_JANUS | Target: GANYMEDE
 - UTC start time: 2032-02-13T22:00:00.000
 - UTC stop time: 2032-02-14T00:00:00.000
 - Nb of pts: 721

Instrument FOVs on a Map#

By default, the trajectory represented with inst_traj correspond to the surface intersection with the instrument boresight on the target surface. In general, this point is in the middle of the instrument field of view (FOV). However, instead of representing only the boresight intersection on the surface (like we seen before), it is possible to represent the complete instrument field of view patches on the surface. To do that, you can use the .fovs property to retrieve a FovsCollection object that can be added on the map:

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(projection=GANYMEDE)

ax.add_collection(janus_flyby.fovs)

ax.set_title('JANUS footprints during 2G2 flyby (Feb. 13th 2032)');
../../_images/31ddbc6f614ab950d72c803ef9b5bf24476418562338ed3adafa2a57f882e9fc.png

The color of the patches and their contours can be customized by the user with an InstrumentTrajectory property:

Caution

By default, the patches are represented in chronological order and can mask the smaller patches below them. To solve that problem, you can add a sort argument with a given property name to reorder the patches before displaying them.

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(projection=GANYMEDE)

ax.add_collection(janus_flyby.fovs(facecolors='inc',
                                   vmin=0, vmax=90, cmap='hot_r',
                                   sort='inc'))

ax.colorbar(vmin=0, vmax=90, label='inc', cmap='hot_r')

ax.set_title('JANUS footprints during 2G2 flyby (Feb. 13th 2032)');
../../_images/bfd4ae952237b5ada23075dd85363faefdf601831c711afd87c6694415886496.png

Customize the colorbar#

You can customize the colorbar and change its label and ticks. For example, let say that you want to display JANUS pixel scale, not in km/pix but in m/pixel, you can import a custom formatting ticks (m_pix_ticks) and provide it to the format attribute.

Tip

You can add a reverse boolean argument to change how the patches are sorted (ascending or descending values). By default, the sort goes from the small values first (below) to the large values (on top), except for inc, dist and alt that are expected to be reversed.

from planetary_coverage.ticks import m_pix_ticks

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(projection=GANYMEDE)

ax.add_collection(janus_flyby.fovs(facecolors='pixel_scale',
                                   vmin=0.01, vmax=.15,
                                   sort='dist', reverse=True))

ax.colorbar(vmin=0.01, vmax=0.15, label='JANUS pixel scale',
            format=m_pix_ticks, extend='max')

ax.set_title('JANUS footprints during 2G2 flyby (Feb. 13th 2032)');
../../_images/1922c75c83e9c2c250112d3e6727b4a89d287c118cfb79ceedaa63d666d0ba0a.png

Caution

In a previous release, we introduce an ax.twin_colorbar() helper to represent dual values (altitude vs. pixel scale). This method, shown below, still works and can be used to get a rough estimate of the instrument pixel resolution for a SpacecraftTrajectory in nadir looking geometry. You must keep in mind that pixel_scale in InstrumentTrajectory objects provide an better representation of the pixel scale projected on the surface where the instrument boresight actually intersect the surface. We highly recommend to use the method above, rather than the method below to represent the instrument pixel scale.

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(projection=GANYMEDE)

ax.add_collection(janus_flyby.fovs(facecolors='alt',
                                   vmin=500, vmax=10_000,
                                   sort='dist', reverse=True))

ax.colorbar(vmin=500, vmax=10_000, label='alt', extend='max')
ax.twin_colorbar(label='JANUS pixel scale',
                 ticks=[25, 50, 75, 100, 125, 150],
                 format=janus_flyby.observer.ifov_cross_track * m_pix_ticks)

ax.set_title('JANUS footprints during 2G2 flyby (Feb. 13th 2032)');
../../_images/ad8fbb93a43120f25410a8b3aead20e9a069f8497ed1feab5e655c1f7a5ec83a.png

Customize the number of points in the FOV contour#

By default, every field of view is composed of 25 points on its contour to properly represent most of the shapes accounted in the instrument kernels.

traj = tour['2032-02-13T21'].new_traj(instrument='JANUS')

traj.fovs.pts.shape
(4, 1, 25)

Added in version 1.2.0.

However in some case, you may want to increase the number of points to draw more precise polygons on the surface or decrease this number to gain in performance:

traj.fovs.npts = 5  # Decrease the number of points in the FOV

traj.fovs.pts.shape
(4, 1, 5)

If the FOV has a RECTANGULAR and POLYGON shape, it is possible to provide a tuple that will correspond to the number of points per edges (excluding the corners):

Note

If the tuple size is smaller than the number of edges/corners (like here), its values will be cycled.

traj.fovs.npts = (3, 1)

traj.fovs.pts.shape
(4, 1, 13)

Here is the corresponding points distribution in the FOV after interpolation:

  C0 -> +3 pts -> C1
  ^               |
+1 pt           +1 pt
  |               v
  C3 <- +3 pts <- C2

The final (closed) sequence is [C0, P0, P1, P2, C1, P3, C2, P4, P5, P6, C3, P7, C0] = 13 pts.

Hide code cell source

_, axes = plt.subplots(1, 3, figsize=(12, 4))

traj.fovs.npts = 25
axes[0].set_title('traj.fovs.npts = 25 (default)')
axes[0].plot(*traj.fovs.rlonlat[1:], 'o', color='C0')
axes[0].add_collection(traj.fovs(facecolors='C0', alpha=0.3))

traj.fovs.npts = 5
axes[1].set_title('traj.fovs.npts = 5')
axes[1].plot(*traj.fovs.rlonlat[1:], 'o', color='C1')
axes[1].add_collection(traj.fovs(facecolors='C1', alpha=0.3))

traj.fovs.npts = (3, 1)
axes[2].set_title('traj.fovs.npts = (3, 1)')
axes[2].plot(*traj.fovs.rlonlat[1:], 'o', color='C2')
axes[2].add_collection(traj.fovs(facecolors='C2', alpha=0.3));
../../_images/1e69044b94cecd40aea828639d38c87af8ebcd812b47da08f5f621eab9034d10.png

Disable pixels at the limb in FOV contour#

Note

Previously, if at least one pixel was present in the limb, the whole FOV was not represented when traj.fovs.limb_contour = False. Now, all the points on the surface will be represented.

Added in version 1.2.0.

In some cases, when the spacecraft is very close to the target body, the approximation of the limb contour based on the coordinates of its impact parameter is not longer valid (and could lead to cusps). It is now possible to disable the representation of the pixels on the limb and only represent the ones that are intersecting the surface:

traj.fovs.limb_contour = False

Hide code cell source

traj = tour['2032-02-13T17'].new_traj(instrument='JANUS')
traj.fovs.npts = 100  # Increase the number of points on the contour

fig = plt.figure(figsize=(18, 12))

ax0 = fig.add_subplot(211, projection=GANYMEDE)
ax1 = fig.add_subplot(212, projection=GANYMEDE)

traj.fovs.limb_contour = True
ax0.set_title('traj.fovs.limb_contour = True (default)')
ax0.add_collection(traj.fovs(facecolors='C1',  edgecolors='C0', alpha=0.5))
ax0.scatter(traj.fovs.rlonlat[1], traj.fovs.rlonlat[2], c=np.where(traj.fovs.surface, 'tab:blue', 'tab:red')[0], s=10)

traj.fovs.limb_contour = False
ax1.set_title('traj.fovs.limb_contour = False')
ax1.add_collection(traj.fovs(facecolors='C1',  edgecolors='C0', alpha=0.5))
ax1.scatter(traj.fovs.rlonlat[1], traj.fovs.rlonlat[2], c=np.where(traj.fovs.surface, 'tab:blue', 'tab:red')[0], s=10);
../../_images/96433a01e8348f0d7d67e6a2faee910c0a9a24522ac6bba9a941d8e95e86e03c.png

Similarly to Trajectory.surface and Trajectory.limb; it is possible to identify which points on the contour are either on the surface or on the limb:

traj.fovs.surface
array([[False, False,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True,  True,  True,  True,
         True,  True,  True,  True,  True,  True, False]])
traj.fovs.limb
array([[ True,  True, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False, False, False, False,
        False, False, False, False, False, False,  True]])

It is also possible to identify if at one point is on the surface (respectively on the limb) and if all are:

traj.fovs.surface_any  # respectively traj.fovs.limb_any
array([ True])
traj.fovs.surface_all  # respectively traj.fovs.limb_all
array([False])