diff --git a/geomagio/edge/EdgeFactory.py b/geomagio/edge/EdgeFactory.py
new file mode 100644
index 0000000000000000000000000000000000000000..71503700cabd58e149ef1c81a38a493a0cfe1570
--- /dev/null
+++ b/geomagio/edge/EdgeFactory.py
@@ -0,0 +1,441 @@
+"""Factory that loads data from earthworm and writes to Edge."""
+
+import obspy.core
+from geomagio import TimeseriesFactory, TimeseriesFactoryException
+from obspy import earthworm
+from ObservatoryMetadata import ObservatoryMetadata
+
+
+class EdgeFactory(TimeseriesFactory):
+
+    def __init__(self, host=None, port=None, observatory=None,
+            channels=None, type=None, interval=None, ):
+        TimeseriesFactory.__init__(self, observatory, channels, type, interval)
+        self.client = earthworm.Client(host, port)
+
+    def get_timeseries(self, starttime, endtime, observatory=None,
+            channels=None, type=None, interval=None):
+        """Get timeseries data
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code.
+        starttime : obspy.core.UTCDateTime
+            time of first sample.
+        endtime : obspy.core.UTCDateTime
+            time of last sample.
+        channels : array_like
+            list of channels to load
+        type : {'variation', 'quasi-definitive'}
+            data type.
+        interval : {'minute', 'second'}
+            data interval.
+
+        Returns
+        -------
+        obspy.core.Stream
+            timeseries object with requested data.
+
+        Raises
+        ------
+        TimeseriesFactoryException
+            if invalid values are requested, or errors occur while
+            retrieving timeseries.
+        """
+        observatory = observatory or self.observatory
+        channels = channels or self.channels
+        type = type or self.type
+        interval = interval or self.interval
+        channels = channels or self.channels
+
+        timeseries = None
+        for channel in channels:
+            data = self._get_timeseries(starttime, endtime, observatory,
+                    channel, type, interval)
+            if timeseries is None:
+                timeseries = data
+            else:
+                timeseries += data
+
+        return timeseries
+
+    def put_timeseries(self, starttime, endtime, observatory=None,
+                channels=None, type=None, interval=None):
+        """Put timeseries data
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code.
+        starttime : obspy.core.UTCDateTime
+            time of first sample.
+        endtime : obspy.core.UTCDateTime
+            time of last sample.
+        channels : array_like
+            list of channels to load
+        type : {'variation', 'quasi-definitive'}
+            data type.
+        interval : {'minute', 'second'}
+            data interval.
+
+        Returns
+        -------
+        obspy.core.Stream
+            timeseries object with requested data.
+
+        Raises
+        ------
+        TimeseriesFactoryException
+            if invalid values are requested, or errors occur while
+            retrieving timeseries.
+        """
+        raise NotImplementedError('"get_timeseries" not implemented')
+
+    def get_edge_channel_codes(self, observatory, channels, type, interval):
+        """Get Edge channel(s) codes given single character channel(s)
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        array_like
+            list of corresponding edge channel names {MVH, SVH, MVE, SVE, ...}
+        """
+        earthworm_channels = []
+        for channel in channels:
+            earthworm_channels.append(self._get_edge_channel(observatory,
+                channel, type, interval))
+        return earthworm_channels
+
+    def get_interval_from_edge(self, channels):
+        """Get interval from edge style channel codes
+
+        Parameters
+        ----------
+        channels: array_like
+            list of edge channel codes (MVH, MVE, etc)
+
+        Returns
+        -------
+        channels: array_like
+            list of channel codes (H, E, D, Z, etc)
+        """
+        interval = None
+        for channel in channels:
+            if interval is not None and interval is not channel[0]:
+                raise TimeseriesFactoryException(
+                    'Mixed interval values"%s" "%s"' % (interval, channel[0]))
+            interval = channel[0]
+        return self._get_interval_from_code(interval)
+
+    def get_type_from_edge(self, location):
+        """Get type from edge location
+
+        Parameters
+        ----------
+        location: {R0, R1, Q0, D0}
+            the edge location code.
+
+        Returns
+        -------
+        type: {variation, quasi-definitive, definitive}
+            the type of data
+        """
+        type = None
+        if 'location' == 'R0' or 'location' == 'R1':
+            type = 'variation'
+        elif 'location' == 'Q0':
+            type = 'quasi-definitive'
+        elif 'location' == 'D0':
+            type = 'definitive'
+        return type
+
+    def get_channel_code_from_edge(self, channel):
+        """Get channel code from edge channel code.
+
+        Parameters
+        ----------
+        channel: str
+            An edge style channel code (MVH, MVE, etc)
+
+        Returns
+        -------
+        channel: str
+            A single character channel code (H, E, Z, F, etc)
+
+        Raises
+        ------
+        TimeseriesFactoryException
+            If input channel is invalid.
+        """
+        if len(channel) == 3:
+            code = channel[2]
+        else:
+            raise TimeseriesFactoryException(
+                'Unexpected Edge Channel"%s"' % channel)
+        return code
+
+    def _get_edge_network(self, observatory, channel, type, interval):
+        """get edge network code.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        network
+            always NT
+        """
+        return 'NT'
+
+    def _get_edge_station(self, observatory, channel, type, interval):
+        """get edge station.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        station
+            the observatory is returned as the station
+        """
+        return observatory
+
+    def _get_edge_channel(self, observatory, channel, type, interval):
+        """get edge channel.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        edge_channel
+            {MVH, MVE, MVD, etc}
+        """
+        edge_interval_code = self._get_interval_code(interval)
+        edge_channel = None
+        if channel == 'D':
+            edge_channel = edge_interval_code + 'VD'
+        elif channel == 'E':
+            edge_channel = edge_interval_code + 'VE'
+        elif channel == 'F':
+            edge_channel = edge_interval_code + 'SF'
+        elif channel == 'H':
+            edge_channel = edge_interval_code + 'VH'
+        elif channel == 'Z':
+            edge_channel = edge_interval_code + 'VZ'
+        else:
+            raise TimeseriesFactoryException(
+                'Unexpected channel code "%s"' % channel)
+        return edge_channel
+
+    def _get_edge_location(self, observatory, channel, type, interval):
+        """get edge location.
+
+        The edge location code is currently determined by the type
+            passed in.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        location
+            returns an edge location code
+        """
+        location = None
+        if type == 'variation':
+            location = 'R0'
+        elif type == 'quasi-definitive':
+            location = 'Q0'
+        elif type == 'definite':
+            location = 'D0'
+        return location
+
+    def _get_edge_code_from_channel(self, channel):
+        """get edge code from channel.
+
+        The second character of the edge channel code for geomag represents
+            the instrument type.  Currently Variometer and Scalar are
+            supported.  Which one is currently decided by the channel
+            passed in.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        channel_code
+            partial channel code
+        """
+        edge_channel = None
+        if channel == 'D':
+            edge_channel = 'VD'
+        elif channel == 'E':
+            edge_channel = 'VE'
+        elif channel == 'F':
+            edge_channel = 'SF'
+        elif channel == 'H':
+            edge_channel = 'VH'
+        elif channel == 'Z':
+            edge_channel = 'VZ'
+        else:
+            raise TimeseriesFactoryException(
+                'Unexpected channel code "%s"' % channel)
+        return edge_channel
+
+    def _get_interval_from_code(self, interval):
+        """get interval from edge Code.
+
+        The first character of an Edge code represents the interval.
+            Currently minute and second are represented.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        interval type
+        """
+        interval_code = None
+        if 'M':
+            interval_code = 'minute'
+        elif 'S':
+            interval_code = 'second'
+        else:
+            raise TimeseriesFactoryException(
+                'Unexpected interval code "%s' % interval)
+        return interval_code
+
+    def _get_interval_code(self, interval):
+        """get edge interval code.
+
+        Converts the metadata interval string, into an edge single character
+            edge code.
+
+        Parameters
+        ----------
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        interval type
+        """
+        interval_code = None
+        if interval == 'daily':
+            interval_code = 'D'
+        elif interval == 'hourly':
+            interval_code = 'H'
+        elif interval == 'minute':
+            interval_code = 'M'
+        elif interval == 'second':
+            interval_code = 'S'
+        else:
+            raise TimeseriesFactoryException(
+                    'Unexpected interval "%s"' % interval)
+        return interval_code
+
+    def _get_timeseries(self, starttime, endtime, observatory,
+                channel, type, interval):
+        """get timeseries data for a single channel.
+
+        Parameters
+        ----------
+        starttime: obspy.core.UTCDateTime
+            the starttime of the requested data
+        endtime: obspy.core.UTCDateTime
+            the endtime of the requested data
+        observatory : str
+            observatory code
+        channels : array_like
+            list of single character channels {H, E, D, Z, F}
+        type : str
+            data type {Definitive, Quasi-definitive, Variation}
+        interval : str
+            interval length {minute, second}
+
+        Returns
+        -------
+        obspy.core.trace
+            timeseries trace of the requested channel data
+        """
+        station = self._get_edge_station(observatory, channel,
+                type, interval)
+        location = self._get_edge_location(observatory, channel,
+                type, interval)
+        network = self._get_edge_network(observatory, channel,
+                type, interval)
+        channel = self._get_edge_channel(observatory, channel,
+                type, interval)
+        data = self.client.getWaveform(network, station, location,
+                channel, starttime, endtime)
+        stats = obspy.core.Stats(data[0].stats)
+        stats = ObservatoryMetadata().set_metadata(stats, observatory,
+                channel, type, interval)
+        data[0].stats = stats
+        return data
diff --git a/geomagio/edge/EdgeFactory_test.py b/geomagio/edge/EdgeFactory_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9c50b6c6956c11aa34abf0115823882b53471a1
--- /dev/null
+++ b/geomagio/edge/EdgeFactory_test.py
@@ -0,0 +1,61 @@
+"""Tests for EdgeFactory.py"""
+
+from obspy.core.utcdatetime import UTCDateTime
+from EdgeFactory import EdgeFactory
+from nose.tools import assert_equals
+from nose.tools import assert_raises
+from geomagio import TimeseriesFactoryException
+
+
+def test_get_edge_channel_codes():
+    """geomagio.edge.EdgeFactory_test.test_get_edge_channel_codes()
+    """
+    # 1) Call get_edge_channel_codes with minute, variation and H,
+    #    expect MVH back
+    channels = EdgeFactory().get_edge_channel_codes('BOU', ('H'), 'variation',
+        'minute')
+    assert_equals(channels, ['MVH'], 'Expect edge channel to equal MVH')
+    # 2) Call get_edge_channel_codes with second, variation and [H,D,Z,F],
+    #    expect SVN, SVD, SVZ and SSF back
+    channels = EdgeFactory().get_edge_channel_codes('BOU',
+        ('H', 'D', 'Z', 'F'), 'variation', 'second')
+    assert_equals(channels[0], 'SVH', 'Expect edge channels to equal SVH')
+    assert_equals(channels[1], 'SVD', 'Expect edge channels to equal SVD')
+    assert_equals(channels[2], 'SVZ', 'Expect edge channels to equal SVZ')
+    assert_equals(channels[3], 'SSF', 'Expect edge channels to equal SSF')
+
+
+def test_get_interval_from_edge():
+    """geomagio.edge.EdgeFactory_test.test_get_interval_from_edge()
+    """
+    # 1) Call get_interval_from_edge with Minute channels, get minute back.
+    assert_equals(EdgeFactory().get_interval_from_edge(
+        ('MVH', 'MVE')), 'minute')
+    # 2) Call get_interval_from_edge with Mixed channels, raise exception.
+    assert_raises(TimeseriesFactoryException,
+        EdgeFactory().get_interval_from_edge,
+        ('MVH', 'SVE'))
+
+
+def test__get_edge_code_from_channel():
+    """geomagio.edge.EdgeFactory_test.test__get_edge_code_from_channel()
+    """
+    # Call private function _get_edge_code_from_channel, make certain
+    # it gets back the appropriate 2 character code.
+    assert_equals(EdgeFactory()._get_edge_code_from_channel('D'), 'VD')
+    assert_equals(EdgeFactory()._get_edge_code_from_channel('E'), 'VE')
+    assert_equals(EdgeFactory()._get_edge_code_from_channel('F'), 'SF')
+    assert_equals(EdgeFactory()._get_edge_code_from_channel('H'), 'VH')
+    assert_equals(EdgeFactory()._get_edge_code_from_channel('Z'), 'VZ')
+
+
+# def test_get_timeseries():
+def dont_get_timeseries():
+    """geomagio.edge.EdgeFactory_test.test_get_timeseries()"""
+    # Call get_timeseries, and test stats for comfirmation that it came back.
+    edge_factory = EdgeFactory()
+    timeseries = edge_factory.get_timeseries(
+        UTCDateTime(2015, 3, 1, 0, 0, 0), UTCDateTime(2015, 3, 1, 1, 0, 0),
+        'BOU', ('H'), 'variation', 'minute')
+    assert_equals(timeseries.select(channel='MVH')[0].stats.station,
+        'BOU', 'Expect timeseries to have stats')
diff --git a/geomagio/edge/ObservatoryMetadata.py b/geomagio/edge/ObservatoryMetadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d9c50f300d7da4dc7619d0172d5a8dd67fe0fc2
--- /dev/null
+++ b/geomagio/edge/ObservatoryMetadata.py
@@ -0,0 +1,379 @@
+"""Factory that loads metadata for an observatory"""
+
+
+# default metadata for the 14 USGS observatories.
+DefaultMetadata = {'BOU':
+    {'network': 'NT',
+    'station': 'BOU',
+    'channel': 'H',
+    'station_name': 'Boulder',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 40.137,
+    'geodetic_longitude': 254.764,
+    'elevation': 1682,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 7406,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'BRW':
+    {'network': 'NT',
+    'station': 'BRW',
+    'channel': 'H',
+    'station_name': 'Barrow',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 71.322,
+    'geodetic_longitude': 203.378,
+    'elevation': 12,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 16000,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'BSL':
+    {'network': 'NT',
+    'station': 'BSL',
+    'channel': 'H',
+    'station_name': 'Stennis Space Center',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 30.350,
+    'geodetic_longitude': 270.365,
+    'elevation': 8,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 1530,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'CMO':
+    {'network': 'NT',
+    'station': 'CMO',
+    'channel': 'H',
+    'station_name': 'College',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 64.874,
+    'geodetic_longitude': 212.140,
+    'elevation': 197,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 16876,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'DED':
+    {'network': 'NT',
+    'station': 'DED',
+    'channel': 'H',
+    'station_name': 'Deadhorse',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 70.356,
+    'geodetic_longitude': 211.207,
+    'elevation': 10,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 13200,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'FRD':
+    {'network': 'NT',
+    'station': 'FRD',
+    'channel': 'H',
+    'station_name': 'Fredericksburg',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 38.205,
+    'geodetic_longitude': 282.627,
+    'elevation': 69,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 210942,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'FRN':
+    {'network': 'NT',
+    'station': 'FRN',
+    'channel': 'H',
+    'station_name': 'Fresno',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 37.091,
+    'geodetic_longitude': 240.282,
+    'elevation': 331,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 9250,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'GUA':
+    {'network': 'NT',
+    'station': 'GUA',
+    'channel': 'H',
+    'station_name': 'Guam',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 13.588,
+    'geodetic_longitude': 144.867,
+    'elevation': 140,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 1157,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'HON':
+    {'network': 'NT',
+    'station': 'HON',
+    'channel': 'H',
+    'station_name': 'Honolulu',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 21.316,
+    'geodetic_longitude': 202.000,
+    'elevation': 4,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 6920,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'SHU':
+    {'network': 'NT',
+    'station': 'SHU',
+    'channel': 'H',
+    'station_name': 'Shumagin',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 55.348,
+    'geodetic_longitude': 199.538,
+    'elevation': 80,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 13974,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'SIT':
+    {'network': 'NT',
+    'station': 'SIT',
+    'channel': 'H',
+    'station_name': 'Sitka',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 57.058,
+    'geodetic_longitude': 224.674,
+    'elevation': 24,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 16523,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'SJG':
+    {'network': 'NT',
+    'station': 'SJG',
+    'channel': 'H',
+    'station_name': 'San Juan',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 18.113,
+    'geodetic_longitude': 293.849,
+    'elevation': 424,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 209800,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '},
+'TUC':
+    {'network': 'NT',
+    'station': 'TUC',
+    'channel': 'H',
+    'station_name': 'Tucson',
+    'agency_name': 'United States Geological Survey (USGS)',
+    'geodetic_latitude': 32.174,
+    'geodetic_longitude': 249.267,
+    'elevation': 946,
+    'sensor_orientation': 'HDZF',
+    'sensor_sampling_rate': '0.01 second',
+    'data_type': 'variation',
+    'data_interval': 'minute',
+    'data_interval_type': 'filtered 1-minute (00:15-01:45)',
+    'declination_base': 7258,
+    'is_intermagnet': False,
+    'condtions_of_use': 'The Conditions of Use for data provided through ' +
+        'INTERMAGNET and acknowledgement templates can be found at ' +
+        'www.intermagnet.org',
+    'filter_comments': 'Vector 1-minute values are computed from 1-second' +
+        'values using the INTERMAGNET gaussian filter centered on the ' +
+        'minute. Scalar 1-minute values are computed from 1-second values ' +
+        'using the INTERMAGNET gaussian filter centered on the minute. ',
+    'comments': ' # This data file was constructed by the Golden GIN. Final ' +
+        'data will be available on the INTERMAGNET DVD. Go to ' +
+        'www.intermagnet.org for details on obtaining this product. '}
+}
+
+
+class ObservatoryMetadata(object):
+    """Helper class for providing all the metadata needed for a geomag
+          timeseries.
+    Notes
+    -----
+    Currently the only method is set_metadata.  Eventually this will probably
+    pull from a database, or maybe a config file.
+    """
+
+    def set_metadata(self, stats, observatory, channel, type, interval):
+        """Set timeseries metadata (aka a traces stats)
+
+        Parameters
+        ----------
+        stats : obspy.core.trace.stats
+            the class associated with a given obspy trace, which contains
+            it's metadata
+        observatory : string
+            the observatory code to look up.
+        type : {'variation', 'quasi-definitive'}
+            data type.
+        interval : {'minute', 'second'}
+            data interval.
+
+        Returns
+        -------
+        obspy.core.trace.stats
+          the combined stats and the default metadata.
+        """
+        static_stats = DefaultMetadata['BOU']
+        for key in static_stats.keys():
+            if key not in stats:
+                stats[key] = static_stats[key]
+        return stats
diff --git a/geomagio/edge/ObservatoryMetadata_test.py b/geomagio/edge/ObservatoryMetadata_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..933765859bb9cdb5ac275b9707db56513b4c3cfd
--- /dev/null
+++ b/geomagio/edge/ObservatoryMetadata_test.py
@@ -0,0 +1,25 @@
+"""Tests for ObservatoryMetadata.py"""
+
+from ObservatoryMetadata import ObservatoryMetadata
+from nose.tools import assert_equals
+import obspy.core
+
+
+def test_set_metadata():
+    """geomagio.edge.ObservatoryMetadata_test.test_set_metadata()
+    """
+    # Test set_metadata by passing in a stats class, and looking
+    # for parameters that are both passed in, and aquired from the default
+    # metadata.
+    observatorymetadata = ObservatoryMetadata()
+    stats = obspy.core.Stats()
+    stats.channel = 'MVH'
+    stats.location = 'R0'
+    stats.data_interval = 'second'
+    stats.data_type = 'quasi-definitive'
+    stats = observatorymetadata.set_metadata(stats, 'BOU', 'H',
+            'quasi-definitive', 'second')
+    assert_equals(stats['channel'], 'MVH')
+    assert_equals(stats['data_interval'], 'second')
+    assert_equals(stats['data_type'], 'quasi-definitive')
+    assert_equals(stats['declination_base'], 7406)
diff --git a/geomagio/edge/__init__.py b/geomagio/edge/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e2c3fdf9321b5d8d45d538b87c1b579588725e04
--- /dev/null
+++ b/geomagio/edge/__init__.py
@@ -0,0 +1,13 @@
+"""IO Module for Edge Format
+
+Based on documentation at:
+  http://www.ngdc.noaa.gov/IAGA/vdat/iagaformat.html
+"""
+
+from EdgeFactory import EdgeFactory
+from ObservatoryMetadata import ObservatoryMetadata
+
+__all__ = [
+    'EdgeFactory',
+    'ObservatoryMetadata',
+]