G = 6.6740831e-11
class Body:
    def __init__(self, name, mass, equatorial_radius, *, flattening=1, **kwargs):
    def __repr__(self):
    def __getattr__(self, name):
    def mu(self):
    def eccentricity(self):
    def polar_radius(self):
    def propagate(self, date): AU = 149597870700.0 class MeasureSet(UserList):
    @property
    def start(self):
        return self[0].date

    @property
    def stop(self):
        return self[-1].date

    @property
    def dates(self):
        return list({x.date: None for x in self}.keys())

    @property
    def all_dates(self):
        return [x.date for x in self]

    @property
    def types(self):
        return list({x.__class__.__name__: None for x in self}.keys())

    @property
    def sources(self):
        return list({x.frame: None for x in self}.keys())

    @property
    def paths(self):
        return list({x.path: None for x in self if hasattr(x, "path")}.keys())

    def sort(self, **kwargs):
        kwargs.setdefault("key", lambda x: x.date)
        return super().sort(**kwargs)

    def filter(self, *, type=None, src=None, path=None):
        mes = []
        for m in self:
            if (
                None not in (type, src, path)
                and m.type == type
                and m.frame == src
                and m.path == path
            ):
                mes.append(m)
            elif type is not None and m.type == type:
                mes.append(m)
            elif src is not None and m.frame == src:
                mes.append(m)
            elif path is not None and m.path == path:
                mes.append(m)

        return self.__class__(mes) class Range(StationMeasure):
    def from_orbit(self, orb):
        return self.__class__(
            self.path,
            orb.date,
            orb.copy(frame=self.frame, form="spherical").r * (len(self.path) - 1),
        ) class Azimut(StationMeasure):
    def from_orbit(self, orb):
        return self.__class__(
            self.path, orb.date, orb.copy(frame=self.frame, form="spherical").theta
        ) class Elevation(StationMeasure):
    def from_orbit(self, orb):
        return self.__class__(
            self.path, orb.date, orb.copy(frame=self.frame, form="spherical").phi
        ) class Doppler(StationMeasure):
    def from_orbit(self, orb):
        return self.__class__(
            self.path, orb.date, orb.copy(frame=self.frame, form="spherical").r_dot
        ) class CcsdsError(ParseError):
    pass def parse_date(string, scale):
    """Parse a date formated as described in the CCSDS Blue Books"""

    try:
        out = Date.strptime(string, DATE_FMT_DEFAULT, scale=scale)
    except ValueError:
        try:
            out = Date.strptime(string, DATE_FMT_D_OF_Y, scale=scale)
        except ValueError:
            out = Date.strptime(string, DATE_FMT_NO_MSEC, scale=scale)

    return out def dump_kvn_header(data, ccsds_type, version="1.0", **kwargs):

    return """CCSDS_{type}_VERS = {version}
CREATION_DATE = {creation_date:{fmt}}
ORIGINATOR = {originator}
""".format(
        type=ccsds_type.upper(),
        creation_date=Date.now(),
        originator=kwargs.get("originator", "N/A"),
        version=version,
        fmt=DATE_FMT_DEFAULT,
    ) def dump_xml_header(data, ccsds_type, version="1.0", **kwargs):

    attrib = {
        "{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation": "http://sanaregistry.org/r/ndmxml/ndmxml-1.0-master.xsd",
        "id": f"CCSDS_{ccsds_type.upper()}_VERS",
        "version": version,
    }

    top = ET.Element(
        ccsds_type.lower(),
        attrib,
        nsmap={"xsi": "http://www.w3.org/2001/XMLSchema-instance"},
    )
    header = ET.SubElement(top, "header")
    creation_date = ET.SubElement(header, "CREATION_DATE")
    creation_date.text = Date.now().strftime(DATE_FMT_DEFAULT)
    originator = ET.SubElement(header, "ORIGINATOR")
    originator.text = kwargs.get("originator", "N/A")

    return top DATE_FMT_DEFAULT = "%Y-%m-%dT%H:%M:%S.%f" def xml2dict(string):
    """Convert and XML string into nested dicts

    The lowest element will be a Field object
    """

    root = ET.fromstring(string)

    data = {}

    def _recurse(elem):
        data = {}
        for subelem in elem:
            if hasattr(subelem, "text") and subelem.text.strip():
                field = Field(subelem.text, subelem.attrib)
                if subelem.tag not in data:
                    data[subelem.tag] = field
                elif not isinstance(data[subelem.tag], list):
                    data[subelem.tag] = [data[subelem.tag], field]
                else:
                    data[subelem.tag].append(field)
            elif subelem.tag in data:
                if not isinstance(data[subelem.tag], list):
                    # We encounter a new child, but a sibling with the same
                    # tag already exists
                    data[subelem.tag] = [data[subelem.tag], _recurse(subelem)]
                else:
                    data[subelem.tag].append(_recurse(subelem))
            else:
                data[subelem.tag] = _recurse(subelem)
        return data

    return _recurse(root) def get_format(**kwargs):
    """retrieve the format to dump the file into"""
    return kwargs.get(
        "fmt", config.get("io", "ccsds_default_format", fallback=DEFAULT_FMT)
    )
import numpy as np
import lxml.etree as ET
from ...constants import c
from ...utils import units
from ...utils.measures import MeasureSet, Range, Azimut, Elevation, Doppler
from .commons import (
    CcsdsError,
    parse_date,
    dump_kvn_header,
    dump_xml_header,
    DATE_FMT_DEFAULT,
    xml2dict,
    get_format,
)




def loads(string, fmt="kvn"):
    """Read CCSDS TDM format and convert it to a MeasureSet"""

    if fmt == "kvn":
        measures = _loads_kvn(string)
    else:
        measures = _loads_xml(string)

    return measures


def dumps(data, **kwargs):

    fmt = get_format(**kwargs)

    if fmt == "kvn":
        string = _dumps_kvn(data, **kwargs)
    elif fmt == "xml":
        string = _dumps_xml(data, **kwargs)
    else:  # pragma: no cover
        raise CcsdsError(f"Unknown format : {fmt}")

    return string


def _loads_kvn(string):

    mode = "meta"
    meta = {}
    sets = []
    for i, line in enumerate(string.splitlines()):

        if not line or line.startswith("COMMENT"):
            continue
        elif line.startswith("DATA_START"):
            participants = [
                v for k, v in sorted(meta.items()) if k.startswith("PARTICIPANT_")
            ]
            path_txt = meta["PATH"]
            path = [participants[int(p) - 1] for p in path_txt.split(",")]
            scale = meta["TIME_SYSTEM"]
            mode = "data"
            r_unit = units.km
            if meta.get("RANGE_UNITS") == "s":
                r_unit *= c
            data = MeasureSet()
            sets.append(data)
            continue
        elif line.startswith("DATA_STOP"):
            mode = "meta"
            continue

        key, _, value = line.partition("=")
        key = key.strip()
        value = value.strip()

        if mode == "meta":
            meta[key] = value
        elif mode == "data":
            date, value = value.split()
            date = parse_date(date.strip(), scale)
            value = float(value)

            if key == "RANGE":

                obj = Range(path, date, value * r_unit)