package org.simantics.history; import java.io.IOException; import java.math.BigDecimal; import org.simantics.history.csv.ExportInterpolation; import org.simantics.history.util.HistoryExportUtil; import org.simantics.history.util.StreamIterator; import org.simantics.history.util.ValueBand; import gnu.trove.list.array.TDoubleArrayList; public class HistorySampler { public synchronized static TDoubleArrayList sample( HistorySamplerItem item, double from, double end, double timeWindow, double timeStep, boolean resample ) throws HistoryException, IOException { try { // If there is something pending at this point, flush before opening for read if(item.collector != null) item.collector.flush(); item.open(); return sample(item.iter, from, end, timeWindow, timeStep, resample); } finally { item.close(); } } public static TDoubleArrayList sample( StreamIterator iter, double from, double end, double timeWindow, double timeStep, boolean resample ) throws HistoryException, IOException { return sample(iter, from, end, timeWindow, timeStep, resample, 0.0); } public static TDoubleArrayList sample( StreamIterator iter, double from, double end, double timeWindow, double timeStep, boolean resample, Double sampleFrom ) throws HistoryException, IOException { ExportInterpolation numberInterpolation = ExportInterpolation.LINEAR_INTERPOLATION; double startTime = from; if(sampleFrom != null) { // This option can be used do define the offset of sampling. Samples will be sampleFrom + n * timeStep startTime = sampleFrom; } TDoubleArrayList result = new TDoubleArrayList(); if(iter.isEmpty()) return result; double allFrom = iter.getFirstTime(); double allEnd = 10e10;//iter.getLastTime(); if(from > (allEnd + timeStep)) { from = allEnd-timeWindow; end = allEnd; } // System.err.println("sample " + from + " " + end); // if(from < 0) // System.err.println("fgag"); // Prepare time boolean hasAnyValues = allFrom != Double.MAX_VALUE && allEnd != -Double.MAX_VALUE; // Make intersection of actual data range (allFrom, allEnd) and requested data (from, end) double _from = Double.MAX_VALUE, _end = -Double.MAX_VALUE; if (hasAnyValues) { _from = Math.max(allFrom, from); _end = Math.min(allEnd, end); } if (!hasAnyValues) { // System.err.println("=> no values"); return result; } // Iterate until endTime is met for all variables double time = _from; if(!resample) { // If resample is false then all samples are reported as is. The code achieves this by setting startTime to _from and timeStep to 0.0 time = _from; timeStep = 0.0; } else { // time = startTime + n*timeStep // Sampling based on given startTime and timeStep if(timeStep > 0) { // Find the first sample time that contains data double n = Math.max(0, Math.ceil((_from-startTime) / timeStep)); time = startTime + n*timeStep; } else { // Start sampling from startTime but make sure that it is not less than _from if(startTime > _from) time = startTime; } } // Must convert double times to String when initializing BigDecimal. // Otherwise BigDecimal will pick up inaccuracies from beyond 15 precise digits // thus making a mess of the time step calculations. BigDecimal bigTime = new BigDecimal(String.valueOf(time)); BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep)); //System.err.println("=> goto " + time); if(!iter.gotoTime(time)) { //System.err.println("=> no sample found at " + time); return result; } //time = iter.getValueBand().getTimeDouble(); boolean ignore = Math.abs(time-from) < 1e-6; do { //System.err.println("process " + time + " " + iter.getValueBand()); // Check for valid value if ( iter.hasValidValue() ) { // Write time if(!ignore) { // System.err.println("Add time : " + time); result.add(time); } // Write value Object value = iter.getValueBand().getValue(); //System.err.print("Add value : " + value); if (value instanceof Number) { if (value instanceof Float || value instanceof Double) { switch (numberInterpolation) { case PREVIOUS_SAMPLE: if(!ignore) { // System.err.println(" previous .. done!"); result.add(((Number) value).doubleValue()); } break; case LINEAR_INTERPOLATION: if (time != iter.getValueBand().getTimeDouble() && iter.hasNext()) { // Interpolate int currentIndex = iter.getIndex(); ValueBand band = iter.getValueBand(); //double t1 = band.getTimeDouble(); Number v1 = (Number) value; double t12 = band.getEndTimeDouble(); iter.next(); double t2 = iter.getValueBand().getTimeDouble(); Number v2 = (Number) iter.getValueBand().getValue(); iter.gotoIndex(currentIndex); double vs = v1.doubleValue(); if(time > t12) vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time); if(!ignore) { // System.err.println(" linear .. done!"); result.add(vs); } } else { // Exact timestamp match, or last sample. // Don't interpolate nor extrapolate. if(!ignore) { // System.err.println(" else .. done!"); result.add(((Number) value).doubleValue()); } } break; default: throw new UnsupportedOperationException("Unsupported interpolation: " + numberInterpolation); } } else { throw new IllegalStateException("Value is not a number " + value); } } else if (value instanceof Boolean) { if(!ignore) result.add( (Boolean)value ? 1.0: 0.0); } else { throw new IllegalStateException("Value is not a number " + value); } } ignore = false; // Read next values, and the following times if ( timeStep>0.0 ) { bigTime = bigTime.add(bigTimeStep); time = bigTime.doubleValue(); } else { // Get smallest end time that is larger than current time Double nextTime = null; // System.out.println("time = "+time); if(!iter.hasNext()) break; Double itemNextTime = iter.getNextTime( time ); // System.err.println(" "+i.label+" nextTime="+itemNextTime); if ( nextTime == null || ( nextTime > itemNextTime && !itemNextTime.equals( time ) ) ) nextTime = itemNextTime; if ( nextTime == null || nextTime.equals( time ) ) break; time = nextTime; } boolean hasMore = false; iter.proceedToTime(time); if(HistoryExportUtil.contains(iter, time)) hasMore = true; if(!hasMore) break; } while (time<=_end); //System.err.println("=> " + Arrays.toString(result.toArray())); return result; } // ------------------------------------------------------------------------ // New history sampling routine, supports mip-mapped subscriptions public synchronized static TDoubleArrayList sample(HistorySamplerItem2 item, double end, double timeWindow, int maxSamples, boolean resample) throws HistoryException, IOException { try { // Avoid div / 0 if (maxSamples <= 0) return new TDoubleArrayList(0); // If there is something pending at this point, flush before opening for read if (item.collector != null) item.collector.flush(); // Open data source with most suitable sampling interval double secondsPerPixel = timeWindow / (double) maxSamples; //System.out.println("SECONDS / PIXEL: " + secondsPerPixel); item.open(secondsPerPixel); return sample(item.iter, end, timeWindow, maxSamples, resample); } finally { item.close(); } } private static TDoubleArrayList sample(StreamIterator iter, double endTime, double timeWindow, int maxSamples, boolean resample) throws HistoryException, IOException { double fromTime = endTime - timeWindow; //System.out.println("sample: [" + fromTime + " .. " + endTime + "] window = " + timeWindow + " s, max samples = " + maxSamples + ", resample = " + resample); ExportInterpolation interpolation = ExportInterpolation.LINEAR_INTERPOLATION; if (iter.isEmpty() || (resample && maxSamples <= 0)) return new TDoubleArrayList(0); double dataFrom = iter.getFirstTime(); double dataEnd = iter.getLastTime(); // Prepare time boolean hasAnyValues = dataFrom != Double.MAX_VALUE && dataEnd != -Double.MAX_VALUE; if (!hasAnyValues) { //System.out.println("=> no values"); return new TDoubleArrayList(0); } // Make intersection of actual data range (allFrom, allEnd) and requested data (from, end) double from = Math.max(fromTime, dataFrom); double end = Math.min(endTime, dataEnd); //System.out.println("data available [" + dataFrom + " .. " + dataEnd + "]"); //System.out.println("will sample between [" + from + " .. " + end + "]"); // Iterate until endTime is met double time = from; double timeStep = 0; if (resample) { timeStep = timeWindow / maxSamples; } else { // If resample is false then all samples are reported as is. // The code achieves this by setting startTime to from and timeStep to 0.0. } // Must convert double times to String when initializing BigDecimal. // Otherwise BigDecimal will pick up inaccuracies from beyond 15 precise digits // thus making a mess of the time step calculations. BigDecimal bigTime = new BigDecimal(String.valueOf(time)); BigDecimal bigTimeStep = new BigDecimal(String.valueOf(timeStep)); //System.out.println("=> goto " + time); TDoubleArrayList result = new TDoubleArrayList(); if (!iter.gotoTime(time)) { //System.out.println("=> no sample found at " + time); return result; } //time = iter.getValueBand().getTimeDouble(); //System.out.println("=> ignore first item?: " + time + " - " + from + " = " + (time-from) + " => " + (Math.abs(time-from) < 1e-6)); //boolean ignore = Math.abs(time-from) < 1e-6; boolean ignore = false; do { //System.out.println("process " + time + " " + iter.getValueBand() + " (ignore = " + ignore + ")"); // Check for valid value if ( iter.hasValidValue() ) { // Write time if (!ignore) { //System.out.println("Add time : " + time); result.add(time); } // Write value Object value = iter.getValueBand().getValue(); //System.out.print("Add value : " + value); if (value instanceof Number) { if (value instanceof Float || value instanceof Double) { switch (interpolation) { case PREVIOUS_SAMPLE: if (!ignore) { //System.out.println(" previous .. done!"); result.add(((Number) value).doubleValue()); } break; case LINEAR_INTERPOLATION: if (time != iter.getValueBand().getTimeDouble() && iter.hasNext()) { // Interpolate int currentIndex = iter.getIndex(); ValueBand band = iter.getValueBand(); //double t1 = band.getTimeDouble(); Number v1 = (Number) value; double t12 = band.getEndTimeDouble(); iter.next(); double t2 = iter.getValueBand().getTimeDouble(); Number v2 = (Number) iter.getValueBand().getValue(); iter.gotoIndex(currentIndex); double vs = v1.doubleValue(); if (time > t12) vs = HistoryExportUtil.biglerp(t12, v1.doubleValue(), t2, v2.doubleValue(), time); if (!ignore) { //System.out.println(" linear .. done!"); result.add(vs); } } else { // Exact timestamp match, or last sample. // Don't interpolate nor extrapolate. if (!ignore) { //System.out.println(" else .. done!"); result.add(((Number) value).doubleValue()); } } break; default: throw new UnsupportedOperationException("Unsupported interpolation: " + interpolation); } } else { throw new IllegalStateException("Value is not a number " + value); } } else if (value instanceof Boolean) { if(!ignore) result.add( (Boolean)value ? 1.0: 0.0); } else { throw new IllegalStateException("Value is not a number " + value); } } ignore = false; // Read next values, and the following times if ( timeStep>0.0 ) { bigTime = bigTime.add(bigTimeStep); time = bigTime.doubleValue(); } else { // Get smallest end time that is larger than current time Double nextTime = null; //System.out.println(" time = "+time); if(!iter.hasNext()) { duplicateLastDataPoint(result, end); break; } Double itemNextTime = iter.getNextTime( time ); //System.out.println(" "+iter.toString()+" nextTime="+itemNextTime); if ( nextTime == null || ( nextTime > itemNextTime && !itemNextTime.equals( time ) ) ) nextTime = itemNextTime; if ( nextTime == null || nextTime.equals( time ) ) break; time = nextTime; } boolean hasMore = false; iter.proceedToTime(time); if (HistoryExportUtil.contains(iter, time)) hasMore = true; if (!hasMore) { if (time <= end) { duplicateLastDataPoint(result, end); } break; } } while (time <= end); //System.out.println("=> [" + result.size() + "]" + Arrays.toString(result.toArray())); //System.out.println("=> [" + result.size() + "]"); return result; } private static void duplicateLastDataPoint(TDoubleArrayList data, double timestamp) { double lastValue = data.get(data.size() - 1); //System.out.println("Duplicating last sample value " + lastValue + " @ " + timestamp); data.add(timestamp); data.add(lastValue); } }