View Javadoc

1   /* ============================================================
2    * JRobin : Pure java implementation of RRDTool's functionality
3    * ============================================================
4    *
5    * Project Info:  http://www.jrobin.org
6    * Project Lead:  Sasa Markovic (saxon@jrobin.org)
7    *
8    * Developers:    Sasa Markovic (saxon@jrobin.org)
9    *
10   *
11   * (C) Copyright 2003-2005, by Sasa Markovic.
12   *
13   * This library is free software; you can redistribute it and/or modify it under the terms
14   * of the GNU Lesser General Public License as published by the Free Software Foundation;
15   * either version 2.1 of the License, or (at your option) any later version.
16   *
17   * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18   * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19   * See the GNU Lesser General Public License for more details.
20   *
21   * You should have received a copy of the GNU Lesser General Public License along with this
22   * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23   * Boston, MA 02111-1307, USA.
24   */
25  package org.jrobin.data;
26  
27  import org.jrobin.core.RrdException;
28  import org.jrobin.core.Util;
29  
30  import java.util.Calendar;
31  import java.util.Date;
32  
33  /**
34   * Class used to interpolate datasource values from the collection of (timestamp, values)
35   * points. This class is suitable for linear interpolation only. <p>
36   * <p/>
37   * Interpolation algorithm returns different values based on the value passed to
38   * {@link #setInterpolationMethod(int) setInterpolationMethod()}. If not set, interpolation
39   * method defaults to standard linear interpolation ({@link #INTERPOLATE_LINEAR}).
40   * Interpolation method handles NaN datasource
41   * values gracefully.<p>
42   */
43  public class LinearInterpolator extends Plottable {
44  	/**
45  	 * constant used to specify LEFT interpolation.
46  	 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
47  	 */
48  	public static final int INTERPOLATE_LEFT = 0;
49  	/**
50  	 * constant used to specify RIGHT interpolation.
51  	 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
52  	 */
53  	public static final int INTERPOLATE_RIGHT = 1;
54  	/**
55  	 * constant used to specify LINEAR interpolation (default interpolation method).
56  	 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
57  	 */
58  	public static final int INTERPOLATE_LINEAR = 2;
59  	/**
60  	 * constant used to specify LINEAR REGRESSION as interpolation method.
61  	 * See {@link #setInterpolationMethod(int) setInterpolationMethod()} for explanation.
62  	 */
63  	public static final int INTERPOLATE_REGRESSION = 3;
64  
65  	private int lastIndexUsed = 0;
66  	private int interpolationMethod = INTERPOLATE_LINEAR;
67  
68  	private long[] timestamps;
69  	private double[] values;
70  
71  	// used only if INTERPOLATE_BESTFIT is specified
72  	double b0 = Double.NaN, b1 = Double.NaN;
73  
74  	/**
75  	 * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values.
76  	 *
77  	 * @param timestamps timestamps in seconds
78  	 * @param values	 corresponding datasource values
79  	 * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if
80  	 *                      timestamps are not ordered, or array lengths are not equal.
81  	 */
82  	public LinearInterpolator(long[] timestamps, double[] values) throws RrdException {
83  		this.timestamps = timestamps;
84  		this.values = values;
85  		validate();
86  	}
87  
88  	/**
89  	 * Creates LinearInterpolator from arrays of timestamps and corresponding datasource values.
90  	 *
91  	 * @param dates  Array of Date objects
92  	 * @param values corresponding datasource values
93  	 * @throws RrdException Thrown if supplied arrays do not contain at least two values, or if
94  	 *                      timestamps are not ordered, or array lengths are not equal.
95  	 */
96  	public LinearInterpolator(Date[] dates, double[] values) throws RrdException {
97  		this.values = values;
98  		timestamps = new long[dates.length];
99  		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 }