"""Computed metadata field registrations for a :class:`ParameterCatalog`.
Defines date-to-metadata transformations (``DDD``, ``GPSWEEK``, ``YYYY``,
``REFFRAME``, etc.) and :func:`register_computed_fields` which wires
them onto a catalog so that parameter values can be derived from a
processing date at query time.
Usage::
from gnss_product_management.specifications.parameters.parameter import ParameterCatalog
from gnss_product_management.utilities.metadata_funcs import register_computed_fields
cat = ParameterCatalog.from_yaml("meta_spec.yaml")
register_computed_fields(cat)
"""
from __future__ import annotations
import datetime
from enum import Enum
# GPS epoch used for GPS-week calculations.
GNSS_START_TIME = datetime.datetime(1980, 1, 6, tzinfo=datetime.timezone.utc)
[docs]
class IGSAntexReferenceFrameType(Enum):
"""IGS ANTEX reference frame identifiers.
Maps each IGS reference frame to its canonical lowercase string
used in ANTEX filenames (e.g. ``igs20.atx``).
"""
IGS05 = "igs05"
IGS08 = "igs08"
IGS14 = "igs14"
IGS20 = "igs20"
IGSR3 = "igsR3"
# ------------------------------------------------------------------
# Helper functions (pure — no registry dependency)
# ------------------------------------------------------------------
def _date_to_doy(date: datetime.datetime) -> str:
"""Convert a datetime to a zero-padded day-of-year string (001–366)."""
doy = date.timetuple().tm_yday
if doy < 10:
return f"00{doy}"
elif doy < 100:
return f"0{doy}"
return str(doy)
# ------------------------------------------------------------------
# Computed field functions
# ------------------------------------------------------------------
def _ddd(date: datetime.datetime) -> str:
"""Compute day-of-year (``DDD``) from *date*."""
return _date_to_doy(date)
def _gpsweek(date: datetime.datetime) -> str:
"""Compute the GPS week number from *date*."""
time_since_epoch = date - GNSS_START_TIME
gps_week = time_since_epoch.days // 7
return str(gps_week)
def _yyyy(date: datetime.datetime) -> str:
"""Return four-digit year (``YYYY``)."""
return date.strftime("%Y")
def _day(date: datetime.datetime) -> str:
"""Return zero-padded day of month (``DD``)."""
return date.strftime("%d").zfill(2)
def _month(date: datetime.datetime) -> str:
"""Return zero-padded month (``MM``)."""
return date.strftime("%m").zfill(2)
def _yy(date: datetime.datetime) -> str:
"""Return two-digit year (``YY``)."""
return date.strftime("%y")
def _hh(date: datetime.datetime) -> str:
"""Return zero-padded hour (``HH``)."""
return date.strftime("%H")
def _mm(date: datetime.datetime) -> str:
"""Return zero-padded minute (``MM``)."""
return date.strftime("%M")
def _refframe(date: datetime.datetime) -> str:
"""Return the IGS ANTEX reference frame string for *date*."""
d = date.date() if isinstance(date, datetime.datetime) else date
if d >= datetime.date(2022, 11, 27):
return IGSAntexReferenceFrameType.IGS20.value
elif d >= datetime.date(2017, 1, 29):
return IGSAntexReferenceFrameType.IGS14.value
elif d >= datetime.date(2011, 4, 17):
return IGSAntexReferenceFrameType.IGS08.value
elif d >= datetime.date(2006, 11, 5):
return IGSAntexReferenceFrameType.IGS05.value
else:
return ""
# ------------------------------------------------------------------
# Registration entry-point
# ------------------------------------------------------------------
# (name, compute_function, pattern) triples registered onto every ParameterCatalog.
_COMPUTED_FIELDS = [
("DDD", _ddd, None),
("GPSWEEK", _gpsweek, None),
("YYYY", _yyyy, None),
("DAY", _day, None),
("MONTH", _month, None),
("YY", _yy, None),
("HH", _hh, None),
("MM", _mm, None),
("REFFRAME", _refframe, None),
]
[docs]
def register_computed_fields(registry) -> None:
"""Register all date-derived computed fields onto *registry*.
Iterates over the built-in ``_COMPUTED_FIELDS`` list and calls
``registry.computed()`` for each one, wiring the pure date
transformation functions into the catalog.
Args:
registry: A :class:`ParameterCatalog` instance to extend.
"""
for name, func, pattern in _COMPUTED_FIELDS:
registry.computed(name=name, pattern=pattern)(func)