Improve SPICE pool, tour, trajectory and toolbox

Benoit Seignovert requested to merge fix-spice-calculations into main

Closes #15 (closed), #17 (closed), #35 (closed), #55 (closed), #66 (closed), #72 (closed), #73 (closed) and #74 (closed).

Add

  • New HTML representation of SpicePool and MetaKernel objects:
SpicePool.add(['naif0012.tls', 'pck00010.tpc'], purge=True)
SpicePool
Kernels Type Size
naif0012.tls 锔 LSK 5 kB
pck00010.tpc 馃獝 PCK 123 kB
mk = Metakernel('juice_plan.tm', kernels='my_kernels/')
mk
馃摎 Meta-kernel for JUICE Dataset v423 -- Planning 20230309_001

This meta-kernel lists the JUICE Planning SPICE kernels
that provide information for the Planning scenario.

The kernels listed in this meta-kernel and the order in which
they are listed are picked to provide the best data available and
the most complete coverage for the JUICE Planning scenario.

This meta-kernel was generated with the Auxiliary Data Conversion
System version: ADCSng v3.6.7.

-------------------------------------------------------------------
Location:
    ../kernels/Juice/mk/juice_plan_v423_20230309_001.tm
SKD version:
    v423_20230309_001
MK identifier:
    juice_plan_v423_20230309_001
$KERNELS
    ../kernels/Juice/
Kernels Type Size
$KERNELS/ck/juice_sc_crema_5_1_150lb_23_1_default_v01.bc 馃摲 CK 9 MB
$KERNELS/ck/juice_sc_crema_5_1_150lb_23_1_comms_v01.bc 馃摲 CK 2 MB
... ... ...
$KERNELS/spk/juice_orbc_000010_230414_310721_v03.bsp 馃殌 SPK 597 kB
  • New .summary and .details properties of SpicePool and MetaKernel objects:
SpicePool.summary
Types Count Size
锔 LSK 1 5 kB
馃獝 PCK 1 123 kB
Total 2 128 kB
SpicePool.details  # same as _repr_html_
Kernels Type Size
naif0012.tls 锔 LSK 5 kB
pck00010.tpc 馃獝 PCK 123 kB
mk.summary
Types Count Size
馃摲 CK 12 95 MB
馃柤锔 FK 10 1 MB
锔 DSK 35 212 MB
馃敩 IK 14 540 kB
锔 LSK 1 5 kB
馃獝 PCK 6 6 MB
馃洶锔 SCLK 2 12 kB
馃殌 SPK 15 259 MB
Total 95 574 MB
mk.details
Kernels Type Size
$KERNELS/ck/juice_sc_crema_5_1_150lb_23_1_default_v01.bc 馃摲 CK 9 MB
$KERNELS/ck/juice_sc_crema_5_1_150lb_23_1_comms_v01.bc 馃摲 CK 2 MB
... ... ...
$KERNELS/spk/juice_orbc_000010_230414_310721_v03.bsp 馃殌 SPK 597 kB
  • New file_size() miscellaneous helper:
>>> from planetary_coverage.misc import file_size
>>> file_size('ck/juice_sc_crema_5_1_150lb_23_1_default_v01.bc')
'9 MB'
  • Add new SpiceFrame reference object (created by SpiceRef promotion):
>>> SpiceRef('J2000')
<SpiceFrame> J2000 (1)

>>> SpiceRef('IAU_GANYMEDE')
<SpiceFrame> IAU_GANYMEDE (10_025)

>>> SpiceRef('JUICE_SPACECRAFT')
<SpiceFrame> JUICE_SPACECRAFT (-28_000)
  • SpiceRef object can now be hashed and return the hash of the SPICE name:
>>> hash(SpiceRef('GANYMEDE'))
8488306870234993416

>>> hash('GAMYMEDE')
8488306870234993416

They can be now be used as dict keys and query with the name of the reference:

>>> ref = SpiceRef('GANYMEDE')
>>> data = {ref: 123}
>>> data['GANYMEDE']
data
  • SpicePool now have a .brief() method to list the temporal coverage of all the bodies in the pool (similar to NAIF brief utility):
>>> SpicePool.brief(fmt='TDB')
{
    <SpiceSpacecraft> JUICE (-28): ('2023-04-05 12:41:33.336 TDB', '2035-10-05 01:58:58.683 TDB'),
    <SpiceFrame> JUICE_SPACECRAFT (-28_000): ('2022-06-01 00:00:00.000 TDB', '2050-01-01 00:00:00.000 TDB'),
    ...
}
  • SpicePool now have a .gaps() method to list the coverage gaps intervals (#55 (closed)):
>>> SpicePool.gaps('JUICE_SPACECRAFT_PLAN', fmt='TDB')
[
    ['2023-11-21 01:29:11.998 TDB', '2024-10-31 02:29:45.998 TDB'],
    ['2024-08-21 08:19:33.998 TDB', '2026-09-27 23:49:02.998 TDB'],
    ['2024-11-04 08:34:55.998 TDB', '2027-11-20 19:56:45.998 TDB'],
    ...,
    ['2035-10-01 10:55:35.998 TDB', '2035-10-02 02:50:52.998 TDB'],
    ['2035-10-02 10:50:52.998 TDB', '2035-10-03 02:46:15.998 TDB'],
    ['2035-10-03 10:46:15.998 TDB', '2035-10-04 02:41:58.998 TDB']
]

The gaps are sorted by increasing start time. Multiple references can be provided at once.

  • Add .gaps() method to TourConfig object:
tour.gaps('JUICE_SPACECRAFT_PLAN', fmt='TDB')
t_start t_end
0 2023-11-21T01:28:02.816 2024-10-31T02:28:36.816
1 2024-08-21T08:18:24.816 2026-09-27T23:47:53.816
2 2024-11-04T08:33:46.816 2027-11-20T19:55:36.816
3 2026-09-28T23:47:53.816 2029-01-17T06:19:53.816
... ... ...

Contrary to SpicePool the result is an EventsList. This allow the user the use theses gaps to filter any Trajectory object:

>>> traj ^ gaps
<MaskedSpacecraftTrajectory> Observer: JUICE | Target: JUPITER
 - First UTC start time: 2023-06-01T00:00:00.000
 - Last UTC stop time: 2025-01-01T00:00:00.000
 - Nb of pts: 236 (+345 masked)
 - Nb of segments: 2
  • New Trajectory.new_traj() and TourConfig.new_tour() methods to change spacecraft, instrument or/and target (#74 (closed)):
new_traj = traj.new_traj(spacecraft=..., instrument=..., target=...)

new_tour = tour.new_tour(spacecraft=..., instrument=..., target=...)

This will ensure that these change are explicit and avoid name collisions.

Note: the Flyby object can not change their target name.

  • New quaternions() function in the SPICE toolbox:
>>> from planetary_coverage.spice import quaternions

>>> quaternions([[1, 0, 0], [0, 1, 0], [0, 0, 1]])  # attitude matrix (3, 3) or (3, 3, N)
[1, 0, 0, 0]
  • New Trajectory.quaternions property:
>>> traj.quaternions
  • New position_of() and distance_to() methods to Trajectory objects computed with target_position() from SPICE toolbox based on spkpos (#15 (closed)):
>>>  traj.position_of('JUPITER')
>>>  traj.distance_to('JUPITER')

>>> from planetary_coverage.spice.toolbox import target_position

>>> target_position('2032-01-01', 'JUICE', 'GANYMEDE')
  • New target_size property and angular_size() method to Trajectory objects and angular_size() method to SPICE toolbox (#15 (closed)):
>>>  traj.target_size               # Ganymede angular size (degrees) seen from Juice (main target)
>>>  traj.angular_size('CALLISTO')  # Callisto angular size (degrees) seen from Juice

>>> from planetary_coverage.spice.toolbox import angular_size
>>> angular_size('2033-01-01', 'JUICE', 'CALLISTO')
  • New ets_at() and utc_at() methods to Trajectory objects to compute times at target with light-time correction:
>>>  traj.ets_at('GANYMEDE')
>>>  traj.utc_at('GANYMEDE')
  • New angle_between() method to Trajectory objects (see #15 (closed)):
>>>  traj.angle_between('+Z', 'JUPITER')  # Separation angle between the spacecraft +Z axis and JUPITER
  • New target_separation() method to Trajectory objects and SPICE toolbox based on trgsep (see #17 (closed)):
>>>  traj.target_separation('CALLISTO')  # Separation between the main trajectory target and Callisto
# or
>>>  traj.target_separation('CALLISTO', 'IO')  # Separation between the Callisto and Io

>>> from planetary_coverage.spice.toolbox import target_separation

>>> target_separation('2032-01-01', 'JUICE', 'GANYMEDE', 'CALLISTO', shape_1='SPHERE', shape_2='SPHERE')
  • New azel() method to SPICE toolbox to convert vector in a reference frame into azimuth and elevation angles:
>>> from planetary_coverage.spice.toolbox import azel
>>> azel([[0, 0, 1, 1], [0, 1, 0, 1], [0, 0, 1, 1]])
[[0, 270, 0, 315], [0, 0, 45, 35.26]]
  • New station_azel() method to Trajectory objects and SPICE toolbox to compute the azimuth and elevation of a body in a tracking station reference frame (see #17 (closed)):
>>>  traj.station_azel('MALARGUE')  # Separation between the main trajectory target and Callisto

>>> from planetary_coverage.spice.toolbox import station_azel

>>> station_azel('2032-01-01', 'MALARGUE', 'JUICE')
  • New jd() decimal Julian Date function to the SPICE module:
>>> from planetary_coverage.spice import jd

>>> jd('2023-08-10 00:05:09')
2_460_166.503_576

Changes

  • SpicePool.windows() now returns the kernels temporal windows in a dict to keep track of the references and the kernels (#72 (closed)):
>>> SpicePool.windows('JUICE', fmt='TDB')
{
    <SpiceSpacecraft> JUICE (-28): {
        'spk/juice_crema_5_1_150lb_23_1_v01.bsp': [
            ['2023-04-05 12:41:33.336 TDB', '2035-10-05 01:58:58.683 TDB']
        ],
        'spk/juice_orbc_000010_230414_310721_v03.bsp': [
            ['2023-04-14 12:41:15.336 TDB', '2031-07-21 22:52:32.230 TDB']
        ]
    }
}

>>> SpicePool.windows('JUICE', 'GANYMEDE', fmt='UTC')
{
      <SpiceSpacecraft> JUICE (-28): {
            'spk/juice_crema_5_1_150lb_23_1_v01.bsp': array([
                ['2023-04-05T12:40:24.151', '2035-10-05T01:57:49.501']
            ], dtype='datetime64[ms]'),
            'spk/juice_orbc_000010_230414_310721_v03.bsp': array([
                ['2023-04-14T12:40:06.151', '2031-07-21T22:51:23.047']
            ], dtype='datetime64[ms]'),
      },
      <SpiceBody> GANYMEDE (503): {
            'spk/jup365_19900101_20500101.bsp': array([
                  ['1990-01-01T00:00:00.000', '2050-01-01T06:00:00.000']
            ], dtype='datetime64[ms]'),
            'spk/noe-5-2017-gal-a-reduced_20200101_20380902.bsp': array([
                  ['2020-01-01T00:00:00.000', '2038-09-02T06:00:00.000']
            ], dtype='datetime64[ms]'),
      },
}
  • SpicePool.coverage() is now computed per reference and not globally (#72 (closed)):
>>> SpicePool.coverage('JUICE', 'GANYMEDE', fmt='TDB')
('2023-04-05 12:41:33.336 TDB', '2035-10-05 01:58:58.683 TDB')

instead of

('2023-04-14 12:41:15.337 TDB', '2031-07-21 22:52:32.230 TDB')

with the previous method. This is now consistent with NAIF to be consistent with NAIF brief utility:

brief juice_plan.tm -t -a

BRIEF -- Version 4.1.0, September 17, 2021 -- Toolkit Version N0067


Summary for all files.

Bodies                             Start of Interval (ET)          End of Interval (ET)
-------                            -----------------------------   -----------------------------
-28 JUICE                          2023 APR 05 12:41:33.336        2035 OCT 05 01:58:58.683
  • SPICE attitude() now returns the C-matrix and not the transpose of the C-matrix:
from planetary_coverage.spice import attitude

attitude(et, 'JUICE_SPACECRAFT')

It also correspond to matrix rotation from J2000 reference frame to the spacecraft or instrument frame.

  • Trajectory.attitude property also reflect this change.

  • SpiceBody, SpiceSpacecraft and SpiceInstrument now returns a SpiceFrame instead of a str:

>>> SpiceBody('GANYMEDE').frame
<SpiceFrame> IAU_GANYMEDE (10_025)
  • SpiceRef object don't display ID: in their representation:
>>> repr(SpiceRef('JUICE'))
JUICE (-28)  # instead of `JUICE (ID: -28)` before

to be consistent with NAIF brief representation.

  • Simplify tdb() method on iterable and add explicit dtype.

  • Refactor SPICE toolbox iterative method to improve performance by 4 to 40%:

image

Fixed

  • Groundtrack velocity formula: np.sqrt(r**2 * ((vlon * np.cos(lat)) ** 2 + vlat**2)) instead of np.sqrt(r**2 * ((vlon * np.cos(lat)) ** 2 + vlat**2) + vr**2) (#35 (closed))

  • Fix ax.twin_colorbar() representation issue:

image

  • Trajectory object now clears its cache properties if the target or the observer is changes (#74 (closed)):
>>> traj.target
'GANYMEDE'
>>> traj.dist
[1, 2, 3, ...]
>>> traj.target = 'Europa'
>>> traj.target
'EUROPA'
>>> traj.dist
[4, 5, 6, ...]

Depreciated

  • Trajectory.sc_attitude property is depreciated in favor of Trajectory.attitude.
  • Deprecate traj['SPACECRAFT'], traj['INSTRUMENT'] and traj['TARGET'] syntax in favor of traj.new_traj().
  • Deprecate traj['SUN'] and traj['SS'] syntax in favor of traj.ss or traj.sun_pos.

Removed

  • Remove limb_ip_pt() method in favor of SPICE native tangpt in intersect_pt() method (#17 (closed)).
  • __getitem()__ method to MaskedTrajectory and SegmentedTrajectory objects (#29) to avoid to change target or observer properties without clearing the properties cache. A ReferenceError is now raises to encourage the user to edit the parent trajectory properties directly:
>>> mask_traj['MAJIS_IR']
ReferenceError: 'Change of target or observer should be performed on the original trajectory not on the masked trajectory.'
Edited by Benoit Seignovert

Merge request reports