Source code for coordio.iers

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# @Author: José Sánchez-Gallego (gallegoj@uw.edu)
# @Date: 2020-08-16
# @Filename: iers.py
# @License: BSD 3-clause (http://www.opensource.org/licenses/BSD-3-Clause)

import os
import urllib.request
import warnings

import numpy

from . import config, sofa
from .exceptions import CoordIOError, CoordIOUserWarning

BASE_URL = 'https://datacenter.iers.org/products/eop/rapid/'


[docs] class IERS: """Wrapper around the IERS bulletins. Parameters ---------- path : str The path where the IERS table, in CSV format, lives or will be saved to. If `None`, defaults to ``config['iers']['path']``. channel : str The IERS channel to use. Right now only ``finals`` is implemented. download : bool If the IERS table is not found on disk or it's out of date, download an updated copy. Attributes ---------- data : ~numpy.ndarray A record array with the parsed IERS data, trimmed to remove rows for which ``UT1-UTC`` is not defined. """ __instance = None def __new__(cls, path=None, channel='finals', download=True): if cls.__instance is not None: if cls.__instance.is_valid(): return cls.__instance else: # We take the default route which takes care of updating the # file. pass obj = super().__new__(cls) obj.data = None obj.channel = channel or config['iers']['channel'] path = path or config['iers']['path'] if channel == 'finals': obj.path = os.path.join(os.path.expanduser(path), 'finals2000A.data.csv') else: raise NotImplementedError('Only finals channels is implemented.') if os.path.exists(obj.path): cls.load_data(obj) if not cls.is_valid(obj): warnings.warn('Current IERS file is out of date. ' 'Redownloading.', CoordIOUserWarning) cls.update_data(obj, channel=obj.channel) else: cls.update_data(obj, channel=obj.channel) # Check data one last time. if not cls.is_valid(obj): raise CoordIOError('IERS table is not valid. ' 'This should not have happened.') cls.__instance = obj return cls.__instance
[docs] def update_data(self, path=None, channel=None): """Update the IERS table, downloading the latest available version.""" path = path or self.path channel = channel or self.channel if not os.path.exists(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) if channel == 'finals': URL = BASE_URL + 'standard/csv/finals.data.csv' warnings.warn(f'Downloading IERS table from {URL}.', CoordIOUserWarning) url = urllib.request.urlopen(URL) data = url.read() with open(path, 'wb') as fd: fd.write(data) url.close() else: raise NotImplementedError('Only finals channels is implemented.') self.load_data(path=path)
def _get_current_jd(self): """Returns the current JD in the scale of the computer clock.""" jd1, jd2 = sofa.get_internal_date() jd = jd1 + jd2 return jd
[docs] def is_valid(self, jd=None, offset=5, download=True): """Determines whether a JD is included in the loaded IERS table. Parameters ---------- jd : float The Julian date to check. There is a certain ambiguity in the format of the MJD in the IERS tables but for most purposes the difference between UTC and TAI should be meaningless for these purposes. offset : int Number of days to look ahead. If ``jd+offset`` is not included in the IERS table the method will return `False`. download : bool Whether to automatically download an updated version of the IERS table if the requested date is not included in the current one. Returns ------- is_valid : `bool` Whether the date is valid within the current (or newly downloaded) IERS table. """ if self.data is None: raise CoordIOError('IERS data has not been loaded') if jd is None: jd = self._get_current_jd() mjd = int(jd - 2400000.5) min_mjd = self.data['MJD'].min() max_mjd = self.data['MJD'].max() if mjd - offset > min_mjd and mjd + offset < max_mjd: return True else: if download: self.update_data() return self.is_valid(jd, offset, download=False) return False
[docs] def load_data(self, path=None): """Loads an IERS table in CSV format from ``path``.""" path = path or self.path if not os.path.exists(path): raise FileNotFoundError(f'File {path!r} not found.') self.data = numpy.genfromtxt(path, delimiter=';', names=True, dtype=None, encoding='UTF-8') # Trim rows without UT1-UTC data self.data = self.data[~numpy.isnan(self.data['UT1UTC'])]
[docs] def get_delta_ut1_utc(self, jd=None, download=True): """Returns the interpolated ``UT1-UTC`` value, in seconds.""" if jd is None: jd = self._get_current_jd() if not self.is_valid(jd, offset=0, download=download): raise CoordIOError('IERS table is out of date.') # Whats the timescale for MJD in the IERS tables? # Probably doesn't matter. mjd = jd - 2400000.5 return numpy.interp(mjd, self.data['MJD'], self.data['UT1UTC'])