From 241b724ff195ed1a146856234f477a7b23d4238e Mon Sep 17 00:00:00 2001
From: Jeremy Fee <jmfee@usgs.gov>
Date: Tue, 6 Nov 2018 17:57:29 -0700
Subject: [PATCH] Add "without_gaps" option to get_stream_start_end_times, add
 "pad_and_trim_trace" method and update pad_timeseries to use it

---
 geomagio/TimeseriesUtility.py | 89 ++++++++++++++++++++++++++---------
 1 file changed, 68 insertions(+), 21 deletions(-)

diff --git a/geomagio/TimeseriesUtility.py b/geomagio/TimeseriesUtility.py
index 385bb0b2..cc6ef041 100644
--- a/geomagio/TimeseriesUtility.py
+++ b/geomagio/TimeseriesUtility.py
@@ -59,7 +59,7 @@ def create_empty_trace(starttime, endtime, observatory,
     return obspy.core.Trace(data, stats)
 
 
-def get_stream_start_end_times(timeseries):
+def get_stream_start_end_times(timeseries, without_gaps=False):
     """get start and end times from a stream.
             Traverses all traces, and find the earliest starttime, and
             the latest endtime.
@@ -73,6 +73,9 @@ def get_stream_start_end_times(timeseries):
     tuple: (starttime, endtime)
         starttime: obspy.core.UTCDateTime
         endtime: obspy.core.UTCDateTime
+
+    NOTE: when the entire timeseries is a gap, and without_gaps is True,
+    the returned endtime will be one delta earlier than starttime.
     """
     starttime = obspy.core.UTCDateTime(datetime.now())
     endtime = obspy.core.UTCDateTime(0)
@@ -81,6 +84,15 @@ def get_stream_start_end_times(timeseries):
             starttime = trace.stats.starttime
         if trace.stats.endtime > endtime:
             endtime = trace.stats.endtime
+    if without_gaps:
+        gaps = get_merged_gaps(get_stream_gaps(timeseries))
+        for gap in gaps:
+            if gap[0] == starttime and gap[1] != endtime:
+                # gap at start of timeseries, move to first data point
+                starttime = gap[2]
+            elif gap[1] == endtime:
+                # gap at end of timeseries
+                endtime = gap[0] - timeseries[0].stats.delta
     return (starttime, endtime)
 
 
@@ -313,9 +325,9 @@ def merge_streams(*streams):
 
 
 def pad_timeseries(timeseries, starttime, endtime):
-    """Realigns timeseries data so the start and endtimes are the same
-        as what was originally asked for, even if the data was during
-        a gap.
+    """Calls pad_and_trim_trace for each trace in a stream.
+
+    Traces should be merged before calling this method.
 
     Parameters
     ----------
@@ -329,20 +341,55 @@ def pad_timeseries(timeseries, starttime, endtime):
     Notes: the original timeseries object is changed.
     """
     for trace in timeseries:
-        trace_starttime = obspy.core.UTCDateTime(trace.stats.starttime)
-        trace_endtime = obspy.core.UTCDateTime(trace.stats.endtime)
-        trace_delta = trace.stats.delta
-        if trace_starttime > starttime:
-            cnt = int((trace_starttime - starttime) / trace_delta)
-            if cnt > 0:
-                trace.data = numpy.concatenate([
-                        numpy.full(cnt, numpy.nan, dtype=numpy.float64),
-                        trace.data])
-                trace_starttime = trace_starttime - trace_delta * cnt
-                trace.stats.starttime = trace_starttime
-        if trace_endtime < endtime:
-            cnt = int((endtime - trace_endtime) / trace.stats.delta)
-            if cnt > 0:
-                trace.data = numpy.concatenate([
-                        trace.data,
-                        numpy.full(cnt, numpy.nan, dtype=numpy.float64)])
+        pad_and_trim_trace(trace, starttime, endtime)
+
+
+def pad_and_trim_trace(trace, starttime, endtime):
+    """Pads and trims trace data so it is in the range [starttime, endtime].
+
+    Uses trace stats to compute start/end times that are consistent with
+    other trace data.  (starttime and endtime are not checked).
+
+    Parameters
+    ----------
+    trace: obspy.core.Trace
+        One trace to be processed
+    starttime: obspy.core.UTCDateTime
+        the starttime of the requested data
+    endtime: obspy.core.UTCDateTime
+        the endtime of the requested data
+
+    Notes: the original timeseries object is changed.
+    """
+    trace_starttime = obspy.core.UTCDateTime(trace.stats.starttime)
+    trace_endtime = obspy.core.UTCDateTime(trace.stats.endtime)
+    trace_delta = trace.stats.delta
+    if trace_starttime < starttime:
+        # trim to starttime
+        cnt = int((starttime - trace_starttime) / trace_delta) + 1
+        if cnt > 0:
+            trace.data = trace.data[cnt + 1:]
+            trace_starttime = trace_starttime + trace_delta * cnt
+            trace.stats.starttime = trace_starttime
+    elif trace_starttime > starttime:
+        # pad to starttime
+        cnt = int((trace_starttime - starttime) / trace_delta)
+        if cnt > 0:
+            trace.data = numpy.concatenate([
+                    numpy.full(cnt, numpy.nan, dtype=numpy.float64),
+                    trace.data])
+            trace_starttime = trace_starttime - trace_delta * cnt
+            trace.stats.starttime = trace_starttime
+    if trace_endtime > endtime:
+        # trim to endtime
+        cnt = int((trace_endtime - endtime) / trace_delta)
+        if cnt > 0:
+            trace.data = trace.data[:-cnt]
+            trace.stats.npts = len(trace.data)
+    elif trace_endtime < endtime:
+        # pad to endtime
+        cnt = int((endtime - trace_endtime) / trace.stats.delta)
+        if cnt > 0:
+            trace.data = numpy.concatenate([
+                    trace.data,
+                    numpy.full(cnt, numpy.nan, dtype=numpy.float64)])
-- 
GitLab