From 0744886fb90ab8c7782eb914ea187b8493a022b7 Mon Sep 17 00:00:00 2001 From: Jeremy Fee <jmfee@usgs.gov> Date: Tue, 2 Dec 2014 21:42:40 -0600 Subject: [PATCH] Renamed iaga2002.Parser to iaga2002.IAGA2002Parser, added docs --- .../iaga2002/{Parser.py => IAGA2002Parser.py} | 136 ++++++++++-------- .../{ParserTest.py => IAGA2002Parser_test.py} | 65 +++++---- 2 files changed, 119 insertions(+), 82 deletions(-) rename src/python/geomag/io/iaga2002/{Parser.py => IAGA2002Parser.py} (55%) rename src/python/geomag/io/iaga2002/{ParserTest.py => IAGA2002Parser_test.py} (71%) diff --git a/src/python/geomag/io/iaga2002/Parser.py b/src/python/geomag/io/iaga2002/IAGA2002Parser.py similarity index 55% rename from src/python/geomag/io/iaga2002/Parser.py rename to src/python/geomag/io/iaga2002/IAGA2002Parser.py index d5c5bb20..30967632 100644 --- a/src/python/geomag/io/iaga2002/Parser.py +++ b/src/python/geomag/io/iaga2002/IAGA2002Parser.py @@ -1,10 +1,9 @@ -""" -Parsing methods for the IAGA2002 Format. -""" +"""Parsing methods for the IAGA2002 Format.""" -from obspy.core.utcdatetime import UTCDateTime import numpy +from obspy.core.utcdatetime import UTCDateTime +from geomag.io import Timeseries # values that represent missing data points in IAGA2002 @@ -12,36 +11,30 @@ EIGHTS = numpy.float64('88888.88') NINES = numpy.float64('99999.99') -def merge_comments(comments): - """ - Combine multi-line, period-delimited comments. - """ - merged = [] - partial = None - for comment in comments: - if partial is None: - partial = comment - else: - partial = partial + ' ' + comment - # comments end with period - if partial.endswith('.'): - merged.append(partial) - partial = None - # comment that doesn't end in a period - if partial is not None: - merged.append(partial) - return merged - - -class Parser(object): - """ - IAGA2002 parser. +class IAGA2002Parser(object): + """IAGA2002 parser. + + Based on documentation at: + http://www.ngdc.noaa.gov/IAGA/vdat/iagaformat.html + + Attributes + ---------- + headers : dict + parsed IAGA headers. + comments : array + parsed comments. + channels : array + parsed channel names. + times : array + parsed timeseries times. + data : dict + keys are channel names (order listed in ``self.channels``). + values are ``numpy.array`` of timeseries values, array values are + ``numpy.nan`` when values are missing. """ def __init__(self): - """ - Create a new IAGA2002 parser. - """ + """Create a new IAGA2002 parser.""" # header fields self.headers = {} # header comments @@ -54,8 +47,12 @@ class Parser(object): self.data = {} def parse(self, data): - """ - Parse a string containing IAGA2002 formatted data. + """Parse a string containing IAGA2002 formatted data. + + Parameters + ---------- + data : str + IAGA 2002 formatted file contents. """ parsing_headers = True lines = data.splitlines() @@ -73,25 +70,28 @@ class Parser(object): else: self._parse_data(line) self._post_process() - return self def _parse_header(self, line): - """ - Parse a header line. + """Parse header line. + + Adds value to ``self.headers``. """ key = line[1:24].strip() value = line[24:69].strip() self.headers[key] = value def _parse_comment(self, line): - """ - Parse a header comment line. + """Parse comment line. + + Adds line to ``self.comments``. """ self.comments.append(line[2:69].strip()) def _parse_channels(self, line): - """ - Parse the data header that contains channel names. + """Parse data header that contains channel names. + + Adds channel names to ``self.channels``. + Creates empty values arrays in ``self.data``. """ iaga_code = self.headers['IAGA CODE'] self.channels.append(line[30:40].strip().replace(iaga_code, '')) @@ -103,8 +103,10 @@ class Parser(object): self.data[channel] = [] def _parse_data(self, line): - """ - Parse one data point in the timeseries + """Parse one data point in the timeseries. + + Adds time to ``self.times``. + Adds channel values to ``self.data``. """ channels = self.channels self.times.append(UTCDateTime(line[0:24])) @@ -114,10 +116,14 @@ class Parser(object): self.data[channels[3]].append(line[61:70].strip()) def _post_process(self): + """Post processing after data is parsed. + + Merges comment lines. + Parses additional comment-based header values. + Converts data to numpy arrays. + Replaces empty values with ``numpy.nan``. """ - Post processing after data is parsed. - """ - self.comments = merge_comments(self.comments) + self.comments = self._merge_comments(self.comments) self.parse_comments() for channel in self.data: data = numpy.array(self.data[channel], dtype=numpy.float64) @@ -127,24 +133,38 @@ class Parser(object): self.data[channel] = data def parse_comments(self): - """ - Parse header values embedded in comments. - """ + """Parse header values embedded in comments.""" for comment in self.comments: if comment.startswith('DECBAS'): # parse DECBAS decbas = comment.replace('DECBAS', '').strip() self.headers['DECBAS'] = decbas[:decbas.find(' ')] + def _merge_comments(self, comments): + """Combine multi-line, period-delimited comments. -def main(data): - """ - Parse and print an IAGA2002 string. - """ - from pprint import pprint - pprint(Parser().parse(data)) - + Parameters + ---------- + comments : array_like + array of comment strings. -if __name__ == '__main__': - import sys - main(sys.stdin.read()) + Returns + ------- + array_like + merged comment strings. + """ + merged = [] + partial = None + for comment in comments: + if partial is None: + partial = comment + else: + partial = partial + ' ' + comment + # comments end with period + if partial.endswith('.'): + merged.append(partial) + partial = None + # comment that doesn't end in a period + if partial is not None: + merged.append(partial) + return merged diff --git a/src/python/geomag/io/iaga2002/ParserTest.py b/src/python/geomag/io/iaga2002/IAGA2002Parser_test.py similarity index 71% rename from src/python/geomag/io/iaga2002/ParserTest.py rename to src/python/geomag/io/iaga2002/IAGA2002Parser_test.py index 30d8a26d..9e759ad2 100644 --- a/src/python/geomag/io/iaga2002/ParserTest.py +++ b/src/python/geomag/io/iaga2002/IAGA2002Parser_test.py @@ -1,9 +1,7 @@ -""" -Tests for the IAGA2002 Parser class. -""" +"""Tests for the IAGA2002 Parser class.""" from nose.tools import assert_equals -from . import Parser +from IAGA2002Parser import IAGA2002Parser IAGA2002_EXAMPLE = \ @@ -44,31 +42,41 @@ DATE TIME DOY BDTH BDTD BDTZ BDTF | 2013-09-01 00:09:00.000 244 21515.04 -28.86 47809.04 52532.10""" -def test_merge_comments(): +def test__merge_comments(): """ - Verify that merge comments merges lines until they end with a period. + geomag.io.iaga2002.IAGA2002Parser_test.test_merge_comments() + + Call the _merge_comments method with 3 lines, + only the middle line ending in a period. + Verify, the first and second line are merged. """ comments = ['line 1', 'line 2.', 'line 3'] assert_equals( - Parser.merge_comments(comments), + IAGA2002Parser()._merge_comments(comments), ['line 1 line 2.', 'line 3']) -def test_parse_header(): +def test__parse_header(): """ - Verify that header is parsed correctly. + geomag.io.iaga2002.IAGA2002Parser_test.test_parse_header() + + Call the _parse_header method with a header. + Verify the header name and value are split at the correct column. """ - parser = Parser.Parser() + parser = IAGA2002Parser() parser._parse_header(' Format ' + 'IAGA-2002 |') assert_equals(parser.headers['Format'], 'IAGA-2002') -def test_parse_comment(): +def test__parse_comment(): """ - Verify that header comment is parsed correctly. + geomag.io.iaga2002.IAGA2002Parser_test.test_parse_header() + + Call the _parse_comment method with a comment. + Verify the comment delimiters are removed. """ - parser = Parser.Parser() + parser = IAGA2002Parser() parser._parse_comment(' # Go to www.intermagnet.org for details on' + ' obtaining this product. |') assert_equals(parser.comments[-1], @@ -76,22 +84,31 @@ def test_parse_comment(): ' obtaining this product.') -def test_parse_decbas(): - """ - Test that DECBAS is being set. +def test__parse_channels(): """ - parser = Parser.Parser() - parser.parse(IAGA2002_EXAMPLE) - assert_equals(parser.headers['DECBAS'], '5527') + geomag.io.iaga2002.IAGA2002Parser_test.test_parse_channels() - -def test_parse_channels(): - """ - Test that channel names are parsed correctly. + Call the _parse_header method with an IAGA CODE header, then call + the _parse_channels method with a channels header line. + Verify the IAGA CODE value is removed from parsed channel names. """ - parser = Parser.Parser() + parser = IAGA2002Parser() parser._parse_header(' IAGA CODE ' + 'BDT |') parser._parse_channels('DATE TIME DOY ' + 'BDTH BDTD BDTZ BDTF |') assert_equals(parser.channels, ['H', 'D', 'Z', 'F']) + + +def test_parse_decbas(): + """ + geomag.io.iaga2002.IAGA2002Parser_test.test_parse_decbas() + + Call the parse method with a portion of an IAGA 2002 File, + which contains a DECBAS header comment. + Verify DECBAS appears in the headers dict, with the expected value. + """ + parser = IAGA2002Parser() + parser.parse(IAGA2002_EXAMPLE) + assert_equals(parser.headers['DECBAS'], '5527') + -- GitLab