Source code for ptr.timeline
"""PTR timeline module."""
from warnings import warn
from .block.slew import SLEW_BLOCK
from .block.utils import gap, insort, is_block
from .datetime import dt, td
from .element import XML, Element
[docs]class Timeline(Element):
"""Planning Timeline Request element.
Parameters
----------
*blocks: ObsBlock, optional
Block element(s) (default: None).
frame: str, optional
Timeline reference frame (default: 'SC').
description: str or list, optional
Timeline description, put as a xml-comment on top of the element.
**attrs:
Timeline attributes keywords.
Note
----
The blocks are automatically sorted. Overlaps
is not allowed and will raise a BlockOverlapError.
Slew blocks are automatically appended to fill
the gaps between the blocks. They should not be
included in the blocks input list (it will return
a TypeError).
"""
def __init__(self, *blocks, frame='SC', **attrs):
super().__init__('timeline', frame=frame, **attrs)
for block in blocks:
self.append(block)
@property
def start(self):
"""Timeline start time."""
return self[0].start if self else None
@property
def end(self):
"""Timeline end time."""
return self[-1].end if self else None
@property
def duration(self):
"""Timeline duration."""
return self.end - self.start if self else None
@property
def xml(self):
"""Element XML object."""
xml = XML.createElement(self.tag)
# Add element attributes
for key, value in self.attrs.items():
if value is not None:
xml.setAttribute(key, str(value))
latest = False
for block in self:
# Add a slew between blocks only when required
if latest and gap(latest, block):
xml.appendChild(SLEW_BLOCK.xml)
# Add child description if present
for desc in block.xml_desc:
xml.appendChild(desc)
xml.appendChild(block.xml)
latest = block
return xml
[docs] @is_block
def append(self, element):
"""Append a new block to the timeline.
Warning
-------
The new block will be added chronologically in
the list of elements. It must fit between the other
blocks.
"""
insort(self._els, element)
element.link_timeline(self) # Link this timeline to this block element
return self
[docs] def insert(self, _, element):
"""Disable index insertion."""
warn('Insertion is always performed chronologically. '
'You should use `.append()` instead.',
SyntaxWarning, stacklevel=2)
return self.append(element)
[docs] def pop(self, key=-1):
"""Remove block from timeline."""
block = super().pop(key)
block.unlink_timeline(self)
return block
[docs] def sort(self):
"""Sort blocks."""
self._els.sort()
[docs] def offset(self, offset, *, ref='start'):
"""Offset timeline blocks globally.
Parameters
----------
offset: str datetime.timedelta or datetime.datetime
Global or relative offset.
ref: str, optional
Boundary reference for relative offset (datetime).
Only ``start|end|center`` are accepted
Raise
-----
KeyError
If the reference keyword is invalid.
Note
----
This operation does not change the duration of the timeline.
"""
try:
delta = td(offset)
except ValueError:
t = dt(offset)
if ref == 'start':
delta = t - self.start.datetime
elif ref == 'end':
delta = t - self.end.datetime
elif ref == 'center':
delta = t - (self.start + self.duration / 2)
else:
raise KeyError('For relative offset, the ref keyword must be '
'in `start|end|center`.') from None
# Move the blocks from the end when delta > 0 to avoid collisions
for block in self if delta.total_seconds() <= 0 else reversed(self):
block.offset(delta)
return self