/*
* Copyright © 2015 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.data2.dataset2.lib.cube;
import co.cask.cdap.api.dataset.lib.cube.Interpolator;
import co.cask.cdap.api.dataset.lib.cube.TimeValue;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import java.util.Collection;
import java.util.Iterator;
import javax.annotation.Nullable;
/**
* Applies given interpolator to a given time series,
* Given a timeseries, interpolates the values at each timestamp between the earliest and
* latest data points if there is no data point available at that timestamp
* and the value at the timestamp can be interpolated.
*
* Interpolating the data just means we're filling in the missing points with something that
* is reasonably likely to have been the true value at that point.
*
* With linear interpolation, the individual time series get transformed into:
* t1 t2 t3 t4 t5
* 5 - - - 3
* 5 5 4 4 3
*
* With step interpolation, the time series get transformed into:
* t1 t2 t3 t4 t5
* 5 - - - 3
* 5 5 5 5 3
*/
class TimeSeriesInterpolator implements Iterable<TimeValue> {
private final Collection<TimeValue> timeSeries;
@Nullable
private final Interpolator interpolator;
private final int resolution;
public TimeSeriesInterpolator(Collection<TimeValue> timeValues,
@Nullable Interpolator interpolator, int resolution) {
this.timeSeries = ImmutableList.copyOf(timeValues);
this.interpolator = interpolator;
this.resolution = resolution;
}
@Override
public Iterator<TimeValue> iterator() {
return new InterpolatedAggregatorIterator();
}
private class InterpolatedAggregatorIterator extends AbstractIterator<TimeValue> {
private long currentTs;
BiDirectionalPeekingIterator timeseries;
InterpolatedAggregatorIterator() {
timeseries = new BiDirectionalPeekingIterator(Iterators.peekingIterator(timeSeries.iterator()));
if (timeseries.hasNext()) {
currentTs = timeseries.peek().getTimestamp();
}
}
@Override
protected TimeValue computeNext() {
long currentTsValue = 0;
// no more data points in the timeseries
if (!timeseries.hasNext()) {
return endOfData();
}
// move the iterator to the next point in this timeseries if this is an actual data point and not interpolated.
if (timeseries.peek().getTimestamp() == currentTs) {
currentTsValue += timeseries.peek().getValue();
timeseries.next();
} else if (interpolator != null && timeseries.peekBefore() != null) {
// don't interpolate unless we're in between data points
currentTsValue += interpolator.interpolate(timeseries.peekBefore(), timeseries.peek(), currentTs);
}
TimeValue output = new TimeValue(currentTs, currentTsValue);
if (timeseries.hasNext()) {
// increment the currentTs by resolution to get the next data point.
currentTs = (interpolator == null) ? timeseries.peek().getTimestamp() : currentTs + resolution;
}
return output;
}
/**
* Iterator that allows peeking on the next value and also retrieving the last value, which will be null
* if there was no last value. Used to allow interpolating between two datapoints in a timeseries.
*/
public final class BiDirectionalPeekingIterator implements PeekingIterator<TimeValue> {
PeekingIterator<TimeValue> iter;
TimeValue lastValue;
public BiDirectionalPeekingIterator(PeekingIterator<TimeValue> iter) {
this.iter = iter;
this.lastValue = null;
}
@Override
public TimeValue peek() {
return iter.peek();
}
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public TimeValue next() {
lastValue = iter.next();
return lastValue;
}
@Override
public void remove() {
iter.remove();
}
public TimeValue peekBefore() {
return lastValue;
}
}
}
}