From 5dae3f1085f0e58fff931c1fda559c1f4a32dad3 Mon Sep 17 00:00:00 2001 From: Jeremy Fee <jmfee@usgs.gov> Date: Wed, 1 Nov 2017 13:57:14 -0600 Subject: [PATCH] Better estimation of data_time in imfv283 --- geomagio/imfv283/IMFV283Parser.py | 124 +++++++++++++++--------- test/imfv283_test/IMFV283Parser_test.py | 36 +++++++ 2 files changed, 115 insertions(+), 45 deletions(-) diff --git a/geomagio/imfv283/IMFV283Parser.py b/geomagio/imfv283/IMFV283Parser.py index 8275cfc1d..6f59bbb15 100644 --- a/geomagio/imfv283/IMFV283Parser.py +++ b/geomagio/imfv283/IMFV283Parser.py @@ -2,12 +2,12 @@ from __future__ import absolute_import from builtins import range -import math import numpy import sys import obspy -from obspy.core import UTCDateTime +from datetime import datetime, timedelta +from obspy import UTCDateTime from . import imfv283_codes # values that represent missing data points in IAGA2002 @@ -99,6 +99,73 @@ class IMFV283Parser(object): sys.stderr.write("Incorrect data line ") sys.stderr.write(line) + def _estimate_data_time(self, transmission, doy, minute, + max_transmit_delay=1800): + """Get data start time for a GOES data packet. + + Parameters + ---------- + transmission : str + goes transmission time in format 'YYDDDHHMMSS', where + YY is 2 digit year (assumed to be years since 2000) + DDD is julian year day + HH is hour of day (24 hour clock) + MM is minute of day + SS is second of day + doy : int + day of year for first data sample + minute : int + minute of day for first data sample + max_transmit_delay : int + maximum delay in seconds between doy+minute and transmission + before attempting to correct gps timing errors. + default 1800 (30 minutes) + + Returns + ------- + data_time : UTCDateTime + estimated time of first data sample + transmit_time : UTCDateTime + time when message was transmitted + corrected : bool + whether a gps timing error was corrected + """ + # convert to datetime + transmit_time = UTCDateTime( + '20' + transmission[0:5] + 'T' + transmission[5:]) + transmit_year = transmit_time.year + # delta should not include first day of year + data_time_delta = timedelta(days=doy - 1, minutes=minute) + data_time = UTCDateTime( + datetime(transmit_year, 1, 1) + data_time_delta) + if data_time > transmit_time: + # data cannot be measured after it is transmitted + data_time = UTCDateTime(datetime(transmit_year - 1, 1, 1)) + \ + data_time_delta + # check transmission delay, to detect gps clock errors + transmit_delay = transmit_time - data_time + if transmit_delay < max_transmit_delay: + return (data_time, transmit_time, False) + # otherwise, try to correct gps timing errors + if transmit_year >= 1999: + # on 1999-08-22, gps clocks reset to 1980-01-06 + # 228 = (UTCDateTime('1999-08-22') - UTCDateTime('1999-01-06')) \ + # / (24 * 60 * 60) + corrected_data_time = data_time + timedelta(days=228) + transmit_delay = transmit_time - corrected_data_time + if transmit_delay < max_transmit_delay: + return (corrected_data_time, transmit_time, True) + if transmit_year >= 2019: + # on 2019-04-07, gps clocks reset to 1980-01-06 + # 91 = (UTCDateTime('2019-04-07') - UTCDateTime('2019-01-06')) \ + # / (24 * 60 * 60) + corrected_data_time = data_time + timedelta(days=91) + transmit_delay = transmit_time - corrected_data_time + if transmit_delay < max_transmit_delay: + return (corrected_data_time, transmit_time, True) + # otherwise return reported data_time + return (data_time, transmit_time, False) + def _get_data(self, header, data): """get data from data packet @@ -155,47 +222,6 @@ class IMFV283Parser(object): return HEADER_SIZE + 1 return HEADER_SIZE - def _get_startime(self, msg_header, goes_header): - """Get Starttime by combining headers. - - Parameters - ---------- - msg_header : dict - header information for the message packet - goes_header : dict - header information for the goes data packet - - Returns - ------- - goes_time : UTCDateTime - The starttime for the goes data packet. - msg_time : UTCDateTime - The starttime for the goes message packet. - Notes - ----- - The goes data packet stores the day of the year, and the minute of - the day. To get a complete starttime, we use the year for the message - and correct for the case where the message came in a new year, vs. - when the data packet was created. - """ - msg_time = msg_header['transmission_time'] - msg_year = 2000 + int(msg_time[0:2]) - msg_day = int(msg_time[2:5]) - - oridinal_time = '%04d%sT%s' % (msg_year, msg_time[2:5], msg_time[5:]) - msg_time = UTCDateTime(oridinal_time) - - if msg_day == 1 and goes_header['day'] >= 365: - msg_year -= 1 - - hour = math.floor(goes_header['minute'] / 60.) - minute = goes_header['minute'] % 60 - - ordinal_time = '%04d%03dT%02d%02d' % (msg_year, goes_header['day'], - hour, minute) - goes_time = UTCDateTime(ordinal_time) - return (goes_time, msg_time) - def _parse_goes_header(self, data): """ parse goes data header @@ -278,9 +304,17 @@ class IMFV283Parser(object): parsed header of the goes data """ - (goes_time, msg_time) = self._get_startime(msg_header, goes_header) + (goes_time, msg_time, corrected) = self._estimate_data_time( + msg_header['transmission_time'], + goes_header['day'], + goes_header['minute']) + if corrected: + sys.stderr.write( + 'corrected gps week number error\n' + + '\ttransmit day={}, reported day={}, corrected day={}\n' + .format(msg_time.julday, goes_header['day'], goes_time.julday)) if (msg_time - goes_time) > (24 * 60): - sys.stderr.write('data over twice as old as the message') + sys.stderr.write('data over twice as old as the message\n') return scale = goes_header['scale'] diff --git a/test/imfv283_test/IMFV283Parser_test.py b/test/imfv283_test/IMFV283Parser_test.py index 3c79f9a2b..e9e3c3bda 100644 --- a/test/imfv283_test/IMFV283Parser_test.py +++ b/test/imfv283_test/IMFV283Parser_test.py @@ -1,6 +1,8 @@ """Tests for the IMFV283 Parser class.""" from nose.tools import assert_equals +from obspy import UTCDateTime + from geomagio.imfv283 import IMFV283Parser, imfv283_codes @@ -33,3 +35,37 @@ def test_parse_goes_header(): 191) goes_header = IMFV283Parser()._parse_goes_header(goes_data) assert_equals(goes_header['day'], 23) + + +def test_estimate_data_time__correct_doy(): + """imfv283_test.IMFV283Parser_test.test_estimate_data_time__correct_doy() + + Use example goes packet from BOU station, with correct goes doy value. + """ + parser = IMFV283Parser() + # BOU aka normal + transmission = '17274013121' + day = 274 + minute = 72 + (data_time, transmit_time, corrected) = \ + parser._estimate_data_time(transmission, day, minute) + assert_equals(data_time, UTCDateTime('2017-10-01T01:12:00Z')) + assert_equals(transmit_time, UTCDateTime('2017-10-01T01:31:21Z')) + assert_equals(corrected, False) + + +def test_estimate_data_time__incorrect_doy(): + """imfv283_test.IMFV283Parser_test.test_estimate_data_time__correct_doy() + + Use example goes packet from BLC station, with incorrect goes doy value. + """ + parser = IMFV283Parser() + # BLC aka 1999 rollover gps issue + transmission = '17274013241' + day = 46 + minute = 78 + (data_time, transmit_time, corrected) = \ + parser._estimate_data_time(transmission, day, minute) + assert_equals(data_time, UTCDateTime('2017-10-01T01:18:00Z')) + assert_equals(transmit_time, UTCDateTime('2017-10-01T01:32:41Z')) + assert_equals(corrected, True) -- GitLab