001 /* ============================================================
002 * JRobin : Pure java implementation of RRDTool's functionality
003 * ============================================================
004 *
005 * Project Info: http://www.jrobin.org
006 * Project Lead: Sasa Markovic (saxon@jrobin.org)
007 *
008 * Developers: Sasa Markovic (saxon@jrobin.org)
009 *
010 *
011 * (C) Copyright 2003-2005, by Sasa Markovic.
012 *
013 * This library is free software; you can redistribute it and/or modify it under the terms
014 * of the GNU Lesser General Public License as published by the Free Software Foundation;
015 * either version 2.1 of the License, or (at your option) any later version.
016 *
017 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
018 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
019 * See the GNU Lesser General Public License for more details.
020 *
021 * You should have received a copy of the GNU Lesser General Public License along with this
022 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
023 * Boston, MA 02111-1307, USA.
024 */
025 package org.jrobin.data;
026
027 import org.jrobin.core.RrdException;
028 import org.jrobin.core.Util;
029
030 import java.util.Calendar;
031 import java.util.Date;
032
033 /**
034 * Class used to interpolate datasource values from the collection of (timestamp, values)
035 * points. This class is suitable for linear interpolation only. <p>
036 * <p/>
037 * Interpolation algorithm returns different values based on the value passed to
038 * {@link #setInterpolationMethod(int) setInterpolationMethod()}. If not set, interpolation
039 * method defaults to standard linear interpolation ({@link #INTERPOLATE_LINEAR}).
040 * Interpolation method handles NaN datasource
041 * values gracefully.<p>
042 */
043 public class LinearInterpolator extends Plottable {
044 /**
045 * constant used to specify LEFT interpolation.
046 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
047 */
048 public static final int INTERPOLATE_LEFT = 0;
049 /**
050 * constant used to specify RIGHT interpolation.
051 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
052 */
053 public static final int INTERPOLATE_RIGHT = 1;
054 /**
055 * constant used to specify LINEAR interpolation (default interpolation method).
056 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
057 */
058 public static final int INTERPOLATE_LINEAR = 2;
059 /**
060 * constant used to specify LINEAR REGRESSION as interpolation method.
061 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
062 */
063 public static final int INTERPOLATE_REGRESSION = 3;
064
065 private int lastIndexUsed = 0;
066 private int interpolationMethod = INTERPOLATE_LINEAR;
067
068 private long[] timestamps;
069 private double[] values;
070
071 // used only if INTERPOLATE_BESTFIT is specified
072 double b0 = Double.NaN, b1 = Double.NaN;
073
074 /**
075 * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values.
076 *
077 * @param timestamps timestamps in seconds
078 * @param values corresponding datasource values
079 * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if
080 * timestamps are not ordered, or array lengths are not equal.
081 */
082 public LinearInterpolator(long[] timestamps, double[] values) throws RrdException {
083 this.timestamps = timestamps;
084 this.values = values;
085 validate();
086 }
087
088 /**
089 * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values.
090 *
091 * @param dates Array of Date objects
092 * @param values corresponding datasource values
093 * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if
094 * timestamps are not ordered, or array lengths are not equal.
095 */
096 public LinearInterpolator(Date[] dates, double[] values) throws RrdException {
097 this.values = values;
098 timestamps = new long[dates.length];
099 for (int i = 0; i < dates.length; i++) {
100 timestamps[i] = Util.getTimestamp(dates[i]);
101 }
102 validate();
103 }
104
105 /**
106 * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values.
107 *
108 * @param dates array of GregorianCalendar objects
109 * @param values corresponding datasource values
110 * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if
111 * timestamps are not ordered, or array lengths are not equal.
112 */
113 public LinearInterpolator(Calendar[] dates, double[] values) throws RrdException {
114 this.values = values;
115 timestamps = new long[dates.length];
116 for (int i = 0; i < dates.length; i++) {
117 timestamps[i] = Util.getTimestamp(dates[i]);
118 }
119 validate();
120 }
121
122 private void validate() throws RrdException {
123 boolean ok = true;
124 if (timestamps.length != values.length || timestamps.length < 2) {
125 ok = false;
126 }
127 for (int i = 0; i < timestamps.length - 1 && ok; i++) {
128 if (timestamps[i] >= timestamps[i + 1]) {
129 ok = false;
130 }
131 }
132 if (!ok) {
133 throw new RrdException("Invalid plottable data supplied");
134 }
135 }
136
137 /**
138 * Sets interpolation method to be used. Suppose that we have two timestamp/value pairs:<br>
139 * <code>(t, 100)</code> and <code>(t + 100, 300)</code>. Here are the results interpolator
140 * returns for t + 50 seconds, for various <code>interpolationMethods</code>:<p>
141 * <ul>
142 * <li><code>INTERPOLATE_LEFT: 100</code>
143 * <li><code>INTERPOLATE_RIGHT: 300</code>
144 * <li><code>INTERPOLATE_LINEAR: 200</code>
145 * </ul>
146 * If not set, interpolation method defaults to <code>INTERPOLATE_LINEAR</code>.<p>
147 * <p/>
148 * The fourth available interpolation method is INTERPOLATE_REGRESSION. This method uses
149 * simple linear regression to interpolate supplied data with a simple straight line which does not
150 * necessarily pass through all data points. The slope of the best-fit line will be chosen so that the
151 * total square distance of real data points from from the best-fit line is at minimum.<p>
152 * <p/>
153 * The full explanation of this inteprolation method can be found
154 * <a href="http://www.tufts.edu/~gdallal/slr.htm">here</a>.<p>
155 *
156 * @param interpolationMethod Should be <code>INTERPOLATE_LEFT</code>,
157 * <code>INTERPOLATE_RIGHT</code>, <code>INTERPOLATE_LINEAR</code> or
158 * <code>INTERPOLATE_REGRESSION</code>. Any other value will be interpreted as
159 * INTERPOLATE_LINEAR (default).
160 */
161 public void setInterpolationMethod(int interpolationMethod) {
162 switch (interpolationMethod) {
163 case INTERPOLATE_REGRESSION:
164 calculateBestFitLine();
165 case INTERPOLATE_LEFT:
166 case INTERPOLATE_RIGHT:
167 case INTERPOLATE_LINEAR:
168 this.interpolationMethod = interpolationMethod;
169 break;
170 default:
171 this.interpolationMethod = INTERPOLATE_LINEAR;
172 }
173 }
174
175 private void calculateBestFitLine() {
176 int count = timestamps.length, validCount = 0;
177 double ts = 0.0, vs = 0.0;
178 for (int i = 0; i < count; i++) {
179 if (!Double.isNaN(values[i])) {
180 ts += timestamps[i];
181 vs += values[i];
182 validCount++;
183 }
184 }
185 if (validCount <= 1) {
186 // just one not-NaN point
187 b0 = b1 = Double.NaN;
188 return;
189 }
190 ts /= validCount;
191 vs /= validCount;
192 double s1 = 0, s2 = 0;
193 for (int i = 0; i < count; i++) {
194 if (!Double.isNaN(values[i])) {
195 double dt = timestamps[i] - ts;
196 double dv = values[i] - vs;
197 s1 += dt * dv;
198 s2 += dt * dt;
199 }
200 }
201 b1 = s1 / s2;
202 b0 = vs - b1 * ts;
203 }
204
205 /**
206 * Method overriden from the base class. This method will be called by the framework. Call
207 * this method only if you need interpolated values in your code.
208 *
209 * @param timestamp timestamp in seconds
210 * @return inteprolated datasource value
211 */
212 public double getValue(long timestamp) {
213 if (interpolationMethod == INTERPOLATE_REGRESSION) {
214 return b0 + b1 * timestamp;
215 }
216 int count = timestamps.length;
217 // check if out of range
218 if (timestamp < timestamps[0] || timestamp > timestamps[count - 1]) {
219 return Double.NaN;
220 }
221 // find matching segment
222 int startIndex = lastIndexUsed;
223 if (timestamp < timestamps[lastIndexUsed]) {
224 // backward reading, shift to the first timestamp
225 startIndex = 0;
226 }
227 for (int i = startIndex; i < count; i++) {
228 if (timestamps[i] == timestamp) {
229 return values[i];
230 }
231 if (i < count - 1 && timestamps[i] < timestamp && timestamp < timestamps[i + 1]) {
232 // matching segment found
233 lastIndexUsed = i;
234 switch (interpolationMethod) {
235 case INTERPOLATE_LEFT:
236 return values[i];
237 case INTERPOLATE_RIGHT:
238 return values[i + 1];
239 case INTERPOLATE_LINEAR:
240 double slope = (values[i + 1] - values[i]) /
241 (timestamps[i + 1] - timestamps[i]);
242 return values[i] + slope * (timestamp - timestamps[i]);
243 default:
244 return Double.NaN;
245 }
246 }
247 }
248 // should not be here ever, but let's satisfy the compiler
249 return Double.NaN;
250 }
251 }