diff --git a/geomagio/JSON/JSONFactory.py b/geomagio/JSON/JSONFactory.py new file mode 100644 index 0000000000000000000000000000000000000000..9cb43039fa776e6a575accfb5fea318152c38bd9 --- /dev/null +++ b/geomagio/JSON/JSONFactory.py @@ -0,0 +1,59 @@ +"""Factory for json files.""" +from __future__ import absolute_import + +import obspy.core +from .. import ChannelConverter +from ..TimeseriesFactory import TimeseriesFactory +from .JSONWriter import JSONWriter + + +class JSONFactory(TimeseriesFactory): + """TimeseriesFactory for IAGA 2002 formatted files. + + Parameters + ---------- + urlTemplate : str + A string that contains any of the following replacement patterns: + - '%(i)s' : interval abbreviation + - '%(interval)s' interval name + - '%(obs)s' lowercase observatory code + - '%(OBS)s' uppercase observatory code + - '%(t)s' type abbreviation + - '%(type)s' type name + - '%(ymd)s' time formatted as YYYYMMDD + """ + + def __init__(self, **kwargs): + TimeseriesFactory.__init__(self, **kwargs) + + # TODO: Write parser method + def parse_string(self, data, observatory=None, interval='minute', + **kwargs): + """Parse the contents of a string in the format of an json file. + + Parameters + ---------- + jsonString : str + string containing IAGA2002 content. + observatory : str + observatory in case headers are unavailable. + parses observatory from headers when available. + Returns + ------- + obspy.core.Stream + parsed data. + """ + pass + + def write_file(self, fh, timeseries, channels): + """writes timeseries data to the given file object. + + Parameters + ---------- + fh: file object + timeseries : obspy.core.Stream + stream containing traces to store. + channels : array_like + list of channels to store + """ + JSONWriter().write(fh, timeseries, channels) diff --git a/geomagio/JSON/JSONWriter.py b/geomagio/JSON/JSONWriter.py new file mode 100644 index 0000000000000000000000000000000000000000..1574a48ee7a1e6360a83f4c01b5a47fc3ad86527 --- /dev/null +++ b/geomagio/JSON/JSONWriter.py @@ -0,0 +1,212 @@ +from __future__ import absolute_import +from builtins import range + +from io import BytesIO +from collections import OrderedDict +from datetime import datetime +import json +import numpy +import textwrap +from .. import ChannelConverter, TimeseriesUtility +from ..edge import EdgeFactory +from ..TimeseriesFactoryException import TimeseriesFactoryException +from ..Util import create_empty_trace + + +class JSONWriter(object): + """JSON writer. + """ + + def __init__(self): + self.dictionary = OrderedDict() + + def write(self, out, timeseries, channels, **kwargs): + """write timeseries to json file + + Parameters + ---------- + out: file object + file object to be written to. could be stdout + timeseries: obspy.core.stream + timeseries object with data to be written + channels: array_like + channels to be written from timeseries object + """ + request = kwargs.get('request') + for channel in channels: + if timeseries.select(channel=channel).count() == 0: + raise TimeseriesFactoryException( + 'Missing channel "%s" for output, available channels %s' % + (channel, str(TimeseriesUtility.get_channels(timeseries)))) + stats = timeseries[0].stats + if len(channels) != 4: + channels = self._pad_to_four_channels(timeseries, channels) + self._format_metadata(stats, channels) + if request: + self.dictionary['metadata']['url'] = 'http://geomag.usgs.gov/ws/edge/?' + request + self._format_times(timeseries, channels) + self._format_data(timeseries, channels, stats) + out.write(json.dumps(self.dictionary, ensure_ascii=True).encode( + 'utf8')) + + def _format_metadata(self, stats, channels): + """format metadata for json file and update dictionary + + Parameters + ---------- + stats: obspy.core.trace.stats + holds the observatory metadata + channels: array_like + channels to be reported. + """ + dict = self.dictionary.copy() + dict['type'] = 'Timeseries' + dict['metadata'] = OrderedDict() + dict['metadata']['intermagnet'] = OrderedDict() + dict['metadata']['intermagnet']['imo'] = OrderedDict() + dict['metadata']['intermagnet']['imo']['iaga_code'] = stats.station + if 'station_name' in stats: + dict['metadata']['intermagnet']['imo']['name'] = stats.station_name + coords = [None] * 3 + if 'geodetic_longitude' in stats: + coords[0] = str(stats.geodetic_longitude) + if 'geodetic_latitude' in stats: + coords[1] = str(stats.geodetic_latitude) + if 'elevation' in stats: + coords[2] = str(stats.elevation) + dict['metadata']['intermagnet']['imo']['coordinates'] = coords + dict['metadata']['intermagnet']['reported_orientation'] = ''.join(channels) + if 'sensor_orientation' in stats: + dict['metadata']['intermagnet']['sensor_orientation'] = stats.sensor_orientation + if 'data_type' in stats: + dict['metadata']['intermagnet']['data_type'] = stats.data_type + if 'sampling_rate' in stats: + if stats.sampling_rate == 1. / 60.: + rate = 60 + elif stats.sampling_rate == 1. / 3600.: + rate = 3600 + elif stats.sampling_rate == 1. / 86400.: + rate = 86400 + else: + rate = 1 + dict['metadata']['intermagnet']['sampling_period'] = str(rate) + if 'sensor_sampling_rate' in stats: + dict['metadata']['intermagnet']['digital_sampling_rate'] = str(1 / stats.sensor_sampling_rate) + dict['metadata']['status'] = 200 + dict['metadata']['generated'] = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ") + self.dictionary = dict + + def _format_times(self, timeseries, channels): + """format times for json file and update dictionary + + Parameters + ---------- + timeseries: obspy.core.stream + timeseries object with data to be written + channels: array_like + channels to be reported. + """ + times = [] + traces = [timeseries.select(channel=c)[0] for c in channels] + starttime = float(traces[0].stats.starttime) + delta = traces[0].stats.delta + for i in range(len(traces[0].data)): + times.append(self._format_time_string( + datetime.utcfromtimestamp(starttime + i * delta))) + self.dictionary['times'] = times + + def _format_time_string(self, time): + """format one time. + + Parameters + ---------- + time : datetime + timestamp for values + + Returns + ------- + unicode + formatted time. + """ + tt = time.timetuple() + return '{0.tm_year:0>4d}-{0.tm_mon:0>2d}-{0.tm_mday:0>2d}T' \ + '{0.tm_hour:0>2d}:{0.tm_min:0>2d}:{0.tm_sec:0>2d}.{1:0>3d}Z'.format( + tt, int(time.microsecond / 1000)) + + def _format_data(self, timeseries, channels, stats): + """Format all data lines. + + Parameters + ---------- + timeseries : obspy.core.Stream + stream containing traces with channel listed in channels + channels : sequence + list and order of channel values to output. + stats: obspy.core.trace.stats + holds the observatory metadata + """ + self.dictionary['values'] = [] + if timeseries.select(channel='D'): + d = timeseries.select(channel='D') + d[0].data = ChannelConverter.get_minutes_from_radians(d[0].data) + values = [] + self.edge = EdgeFactory() + for c in channels: + value_dict = OrderedDict() + trace = timeseries.select(channel=c)[0] + value_dict['id'] = c + value_dict['metadata'] = OrderedDict() + value_dict['metadata']['element'] = c + if 'network' in stats: + value_dict['metadata']['network'] = stats.network + value_dict['metadata']['station'] = stats.station + edge_channel = self.edge._get_edge_channel(stats.station, + c, + stats.data_type, + stats.data_interval) + value_dict['metadata']['channel'] = edge_channel + if 'location' in stats: + value_dict['metadata']['location'] = stats.location + # TODO: Add flag metadata + values += [value_dict] + data = [] + for i in range(len(trace.data)): + if numpy.isnan(trace.data[i]): + data += ['null'] + else: + data += [str(trace.data[i])] + + + value_dict['values'] = data + self.dictionary['values'] += values + + def _pad_to_four_channels(self, timeseries, channels): + padded = channels[:] + for x in range(len(channels), 4): + channel = 'NUL' + padded.append(channel) + timeseries += create_empty_trace(timeseries[0], channel) + return padded + + @classmethod + def format(self, timeseries, channels, **kwargs): + """Get a json formatted string. + + Calls write() with a BytesIO, and returns the output. + + Parameters + ---------- + kwargs + request : query string + timeseries : obspy.core.Stream + + Returns + ------- + unicode + json formatted string. + """ + request = kwargs.get('request') + out = BytesIO() + writer = JSONWriter() + writer.write(out, timeseries, channels, request) + return out.getvalue() diff --git a/geomagio/JSON/__init__.py b/geomagio/JSON/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..900950d367ef0ff7cbcf28970a3917b7d02cdadc --- /dev/null +++ b/geomagio/JSON/__init__.py @@ -0,0 +1,9 @@ +"""IO Module for JSONFactory Format +""" +from __future__ import absolute_import + +from .JSONFactory import JSONFactory +from .JSONWriter import JSONWriter + + +__all__ = ['JSONWriter'] diff --git a/setup.py b/setup.py index 365d79453201d457517e43c340cf8360c99bdfd4..c2fc80c0ad973923338273c525c30a1999e33db7 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ setup( 'geomagio.iaga2002', 'geomagio.imfv122', 'geomagio.imfv283', + 'geomagio.JSON', 'geomagio.pcdcp', 'geomagio.temperature', 'geomagio.vbf'