Planning Timeline Request#

Planning Timeline Request (PTR) files were created by ESA to support the Rosetta mission team in their planing effort to design custom attitude spacecraft pointing.

Its technical description is documented in RO-ESC-IF-5501_i3r4_RSGS_FD_ICD-2.pdf Rosetta Flight Dynamics report (not publicly available).

Disclaimer

The present documentation does not aim to replace any internal PTR description but only provide python helpers to properly format PTR files and validate them against ESA Attitude Generator Module (AGM) simulator.

Example of PTR syntax#

Tip

Even if some PTR have a valid XML header: <?xml version="1.0" encoding="UTF-8"?>, they don’t follow XML syntax rules.

A PTR, usually saved in a .ptr or .ptx file, follows a xml-like syntax:

<prm>
  <body>
    <segment>
      <data>
        <timeline frame="SC">
            <block ref="OBS">
               <metadata>
                  <comment> Occultation by star HD157056 </comment>
                  <comment>PRIME=JANUS</comment>
                  <solarArrays>
                     <fixedRotationAngle units="deg">-50</fixedRotationAngle>
                  </solarArrays>
               </metadata>
               <startTime> 2032-07-02T04:18:40 </startTime>
               <endTime> 2032-07-02T04:32:00 </endTime>
               <attitude ref="inertial">
                  <boresight ref="SC_Zaxis" />
                  <phaseAngle ref="align">
                     <SCAxis frame="SC">
                        <x> 0 </x>
                        <y> -1 </y>
                        <z> 0 </z>
                     </SCAxis>
                     <inertialAxis ref="EU2SC" />
                  </phaseAngle>
                  <target frame="EME2000">
                     <lon units="deg"> 260.504 </lon>
                     <lat units="deg"> -24.999 </lat>
                  </target>
               </attitude>
            </block>
        </timeline>
      </data>
    </segment>
  </body>
</prm>

It should contains a single <segment> and a collection of <block> that correspond to the change of attitude with respect to a metakernel baseline (usually provided by the mission planning team).

You can create a PTR with 2 different ways:

  1. By reading PTR formatted string or a ptx file.

  2. By creating directly a PointingRequestMessage timeline object.

Both methods are described below.

Create PTR from scratch (advanced)#

You can create your own template to generate PTR element, block or request.

PTR Element#

The smallest PTR element is an Element object:

from ptr import Element

By default it takes a tag name and a value:

Element('lon', 260.504)
<lon> 260.504 </lon>

The element tag is automatically, open an close and the value provided in inserted between the tags.

You can add additional attributes:

Element('lon', 260.504, units='degs')
<lon units="degs"> 260.504 </lon>

as well as a description (inserted as a xml-comment):

Element('lon', 260.504, units='degs', description='Longitude of interest')
<!-- Longitude of interest -->
<lon units="degs"> 260.504 </lon>

Iterable list of values will be represented with a double space between the elements:

Element('xAngles', [1.2, 3.45, 6.789], units='deg')
<xAngles units="deg"> 1.2  3.45  6.789 </xAngles>

You can also concatenate multiple elements together:

target = Element(
    'target',
    Element('lon', 260.504, units='degs'),
    Element('lat', -25, units='degs'),
    frame='EME2000',
)

target
<target frame="EME2000">
  <lon units="degs"> 260.504 </lon>
  <lat units="degs"> -25 </lat>
</target>

You can extract its data:

target['lon'].value
260.504

in this case, the value are parsed when it’s possible (int, float, datetime, timedelta and list with double space '  ' separator).

or update them:

target['lon'] = 150

target
<target frame="EME2000">
  <lon units="degs"> 150 </lon>
  <lat units="degs"> -25 </lat>
</target>

If the children elements don’t have attributes, you can defined them with {key: value, ...} python dictionary:

Element('SCAxis', {'x': 0, 'y': -1, 'z': 0}, frame='SC')
<SCAxis frame="SC">
  <x> 0 </x>
  <y> -1 </y>
  <z> 0 </z>
</SCAxis>

Then, it is fairly easy to generate complex PTR object. For example:

def attitude_pointing(target):
    """PTR Attitude pointing block toward a target."""
    return Element(
        'attitude',
        Element('boresight', ref='SC_Zaxis'),
        Element('target', ref=target),
        Element('phaseAngle', {'yDir': False}, ref='powerOptimised'),
        ref='track',
    )

Then you can do:

attitude = attitude_pointing('GANYMEDE')

attitude
<attitude ref="track">
  <boresight ref="SC_Zaxis"/>
  <target ref="GANYMEDE"/>
  <phaseAngle ref="powerOptimised">
    <yDir> False </yDir>
  </phaseAngle>
</attitude>

If needed, you can exact an element from an other element:

attitude['target']
<target ref="GANYMEDE"/>
attitude['target'].attrs['ref']
'GANYMEDE'

Some snippets are already implemented and can be imported directly from the module:

Hint

More snippets will be implemented in the future.

from ptr.snippets import Target

Target(260.504, -25)
<target frame="EME2000">
  <lon units="deg"> 260.504 </lon>
  <lat units="deg"> -25.0 </lat>
</target>

or:

from ptr.axis import xScAxis

xScAxis
<SCAxis frame="SC">
  <x> 1 </x>
  <y> 0 </y>
  <z> 0 </z>
</SCAxis>

You can also create an Element from a literal PTR string with the read_ptr() function:

el = read_ptr('<target frame="EME2000"><lon units="deg">260.504</lon><lat units="deg">-24.999</lat></target>')

el
<target frame="EME2000">
  <lon units="deg"> 260.504 </lon>
  <lat units="deg"> -24.999 </lat>
</target>
type(el)
ptr.element.Element

Finally, any Element object can be saved into a file:

el.save('target_block.ptx')
PosixPath('target_block.ptx')

PTR observation block#

You may often need to create PTR observation block to fill you PTR timeline. You can use an Element object as described above, or you can use an ObsBlock object.

from ptr import ObsBlock

The main advantage of using ObsBlock is its ability to parse datetime string and to add a few methods to manipulate the temporal window:

obs = ObsBlock('2031-07-02T10', '2031-07-02 12:34:56')

obs
<block ref="OBS">
  <startTime> 2031-07-02T10:00:00 </startTime>
  <endTime> 2031-07-02T12:34:56 </endTime>
</block>

You can edit the start or/and end boundary of the block window with absolute or relative values:

Note

The edit() and offset() changes are performed on the block itself. You don’t need to store the result in a new block.

obs.edit(start='2031-07-02T10:20:30', end='+1h')
<block ref="OBS">
  <startTime> 2031-07-02T10:20:30 </startTime>
  <endTime> 2031-07-02T13:34:56 </endTime>
</block>

You can offset the block globally, with a relative value:

obs.offset('-20 mins')
<block ref="OBS">
  <startTime> 2031-07-02T10:00:30 </startTime>
  <endTime> 2031-07-02T13:14:56 </endTime>
</block>

or with an absolute value, in that case, you need to select the part of the block that will be used as a reference (start|center|end):

obs.offset('2031-07-02T12:34:56', ref='end')
<block ref="OBS">
  <startTime> 2031-07-02T09:20:30 </startTime>
  <endTime> 2031-07-02T12:34:56 </endTime>
</block>

You can also compute the duration of the block:

obs.duration
datetime.timedelta(seconds=11666)

Similarly to Element object, it is possible to extends ObsBlock to compose complex observation blocks:

def nav_cam_block(start, end, target):
    """Custom NavCam PTR observation block."""
    return ObsBlock(
        start, end,
        Element(
            'attitude',
            Element('boresight', ref='SC_Zaxis'),
            Element('target', ref=target),
            Element('phaseAngle', {'yDir': False}, ref='powerOptimised'),
            ref='track',
        ),
        metadata='Track Power Optimised C3.0',
    )

Then, you can do:

nav_cam_block('2030-10-31T03:40:00', '2030-10-31T04:15:00', 'Callisto')
<block ref="OBS">
  <metadata>
    <comment> Track Power Optimised C3.0 </comment>
  </metadata>
  <startTime> 2030-10-31T03:40:00 </startTime>
  <endTime> 2030-10-31T04:15:00 </endTime>
  <attitude ref="track">
    <boresight ref="SC_Zaxis"/>
    <target ref="Callisto"/>
    <phaseAngle ref="powerOptimised">
      <yDir> False </yDir>
    </phaseAngle>
  </attitude>
</block>

PTR pointing request message#

When you have a block or a list of blocks, you need to organize them into a pointing request message in order to get a valid PTR that can be submitted to AGM.

from ptr import PointingRequestMessage

For example, if you want to look during 15 minutes to Callisto, Ganymede and Europa with 1 hour apart, you just have to do:

prm = PointingRequestMessage(
    nav_cam_block('2030-10-31T00:00:00', '2030-10-31T00:15:00', 'Callisto'),
    nav_cam_block('2030-10-31T01:00:00', '2030-10-31T01:15:00', 'Ganymede'),
    nav_cam_block('2030-10-31T02:00:00', '2030-10-31T02:15:00', 'Europa')
)

prm
<prm>
  <body>
    <segment>
      <data>
        <timeline frame="SC">
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-10-31T00:00:00 </startTime>
            <endTime> 2030-10-31T00:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Callisto"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
          <block ref="SLEW"/>
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-10-31T01:00:00 </startTime>
            <endTime> 2030-10-31T01:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Ganymede"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
          <block ref="SLEW"/>
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-10-31T02:00:00 </startTime>
            <endTime> 2030-10-31T02:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Europa"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
        </timeline>
      </data>
    </segment>
  </body>
</prm>

The block in the timeline are automatically organized chronologically with any overlap between then. Then two blocks are not continuous, they will be separated with a slew block (you don’t need to add them manually).

You can pick a block and edit it:

prm[0].edit(start='2030-10-31T03', end='+3h 15 mins')
<block ref="OBS">
  <metadata>
    <comment> Track Power Optimised C3.0 </comment>
  </metadata>
  <startTime> 2030-10-31T03:00:00 </startTime>
  <endTime> 2030-10-31T03:30:00 </endTime>
  <attitude ref="track">
    <boresight ref="SC_Zaxis"/>
    <target ref="Callisto"/>
    <phaseAngle ref="powerOptimised">
      <yDir> False </yDir>
    </phaseAngle>
  </attitude>
</block>

and the pointing request message will reflect this change:

prm
<prm>
  <body>
    <segment>
      <data>
        <timeline frame="SC">
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-10-31T01:00:00 </startTime>
            <endTime> 2030-10-31T01:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Ganymede"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
          <block ref="SLEW"/>
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-10-31T02:00:00 </startTime>
            <endTime> 2030-10-31T02:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Europa"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
          <block ref="SLEW"/>
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-10-31T03:00:00 </startTime>
            <endTime> 2030-10-31T03:30:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Callisto"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
        </timeline>
      </data>
    </segment>
  </body>
</prm>

You can also offset the whole timeline at once:

prm.offset('2030-11-01', ref='start')
<prm>
  <body>
    <segment>
      <data>
        <timeline frame="SC">
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-11-01T00:00:00 </startTime>
            <endTime> 2030-11-01T00:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Ganymede"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
          <block ref="SLEW"/>
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-11-01T01:00:00 </startTime>
            <endTime> 2030-11-01T01:15:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Europa"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
          <block ref="SLEW"/>
          <block ref="OBS">
            <metadata>
              <comment> Track Power Optimised C3.0 </comment>
            </metadata>
            <startTime> 2030-11-01T02:00:00 </startTime>
            <endTime> 2030-11-01T02:30:00 </endTime>
            <attitude ref="track">
              <boresight ref="SC_Zaxis"/>
              <target ref="Callisto"/>
              <phaseAngle ref="powerOptimised">
                <yDir> False </yDir>
              </phaseAngle>
            </attitude>
          </block>
        </timeline>
      </data>
    </segment>
  </body>
</prm>