ingo@298: package de.intevation.gnv.chart;
ingo@298: 
sascha@514: import de.intevation.gnv.artifacts.ressource.RessourceFactory;
sascha@514: 
sascha@514: import de.intevation.gnv.geobackend.base.Result;
sascha@514: 
sascha@514: import de.intevation.gnv.state.describedata.KeyValueDescibeData;
sascha@514: 
sascha@514: import de.intevation.gnv.timeseries.gap.TimeGap;
sascha@514: 
ingo@526: import java.text.DateFormat;
ingo@526: import java.text.SimpleDateFormat;
ingo@526: 
ingo@298: import java.util.Collection;
ingo@329: import java.util.Date;
ingo@334: import java.util.HashMap;
ingo@298: import java.util.Iterator;
ingo@298: import java.util.Locale;
ingo@505: import java.util.TimeZone;
ingo@298: 
ingo@298: import org.apache.log4j.Logger;
ingo@298: 
sascha@514: import org.jfree.chart.ChartFactory;
ingo@298: import org.jfree.chart.ChartTheme;
sascha@514: 
ingo@315: import org.jfree.chart.axis.Axis;
ingo@315: import org.jfree.chart.axis.DateAxis;
ingo@526: import org.jfree.chart.axis.DateTickUnit;
ingo@526: import org.jfree.chart.axis.DateTickUnitType;
sascha@779: import org.jfree.chart.axis.TickUnitSource;
ingo@526: import org.jfree.chart.axis.TickUnits;
ingo@526: import org.jfree.chart.axis.ValueAxis;
sascha@514: 
ingo@298: import org.jfree.chart.plot.PlotOrientation;
sascha@514: import org.jfree.chart.plot.XYPlot;
sascha@514: 
ingo@298: import org.jfree.data.general.Series;
sascha@514: 
sascha@514: import org.jfree.data.time.Minute;
ingo@298: import org.jfree.data.time.TimeSeries;
ingo@298: import org.jfree.data.time.TimeSeriesCollection;
ingo@298: 
ingo@298: /**
ingo@767:  * This class is used to create timeseries charts. The domain axis contains
ingo@767:  * multiple date/time objects.
ingo@767:  *
ingo@767:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@298:  */
ingo@298: public class TimeSeriesChart
ingo@298: extends      AbstractXYLineChart
ingo@298: {
ingo@298: 
ingo@767:     /**
ingo@767:      * Constant format which can be useful to format date items. Value is
ingo@767:      * {@value}.
ingo@767:      */
ingo@350:     public static final String DEFAULT_DATE_FORMAT = "dd-MMM-yyyy";
ingo@315: 
ingo@767:     /**
ingo@767:      * Constant field used if no gap detection should be done here. This field
ingo@767:      * is used in @see #getTimeGapValue. Value is {@value}.
ingo@767:      */
ingo@642:     public static final long   NO_TIME_GAP = Long.MAX_VALUE - 1000;
ingo@767: 
ingo@767:     /**
ingo@767:      * Percentage used for gap detection. Its value is {@value}.
ingo@767:      */
ingo@642:     public static final int    GAP_SIZE    = 5; // in percent
ingo@329: 
ingo@767:     /**
ingo@767:      * Logger used for logging with log4j.
ingo@767:      */
ingo@298:     private static Logger log = Logger.getLogger(TimeSeriesChart.class);
ingo@298: 
ingo@298: 
ingo@767:     /**
ingo@767:      * Constructor used to create <code>TimeSeries</code> charts.
ingo@767:      *
ingo@767:      * @param labels Labels used to be displayed in title, subtitle and so on.
ingo@767:      * @param theme ChartTheme used to adjust the rendering of this chart.
ingo@767:      * @param parameters Collection containing a bunch of parameters.
ingo@767:      * @param measurements Collection containing a bunch of measurements.
ingo@767:      * @param dates Collection containing a bunch of date objects.
ingo@767:      * @param result Collection containing a bunch of <code>Result</code>
ingo@767:      * objects which contain the actual data items to be displayed.
ingo@767:      * @param timeGaps Collection with timegap definitions.
ingo@767:      * @param locale Locale used to specify the format of labels, numbers, ...
ingo@767:      * @param linesVisible Render lines between data points if true, otherwise
ingo@767:      * not.
ingo@767:      * @param shapesVisible Render vertices as points if true, otherwise not.
ingo@767:      */
ingo@298:     public TimeSeriesChart(
ingo@298:         ChartLabels labels,
ingo@298:         ChartTheme  theme,
ingo@298:         Collection  parameters,
ingo@298:         Collection  measurements,
ingo@310:         Collection  dates,
ingo@298:         Collection  result,
ingo@310:         Collection  timeGaps,
ingo@327:         Locale      locale,
ingo@327:         boolean     linesVisible,
ingo@327:         boolean     shapesVisible
ingo@298:     ) {
ingo@298:         this.labels           = labels;
ingo@298:         this.theme            = theme;
ingo@298:         this.parameters       = parameters;
ingo@298:         this.measurements     = measurements;
ingo@310:         this.dates            = dates;
ingo@298:         this.resultSet        = result;
ingo@310:         this.timeGaps         = timeGaps;
ingo@298:         this.locale           = locale;
ingo@298:         this.PLOT_ORIENTATION = PlotOrientation.VERTICAL;
ingo@327:         this.linesVisible     = linesVisible;
ingo@327:         this.shapesVisible    = shapesVisible;
ingo@334:         this.datasets         = new HashMap();
ingo@364:         this.ranges           = new HashMap();
ingo@298:     }
ingo@298: 
ingo@298: 
ingo@767:     /**
ingo@767:      * see de.intevation.gnv.chart.AbstractXYLineChart#initChart()
ingo@767:      */
ingo@788:     @Override
ingo@333:     protected void initChart() {
ingo@312:         chart = ChartFactory.createTimeSeriesChart(
ingo@312:             labels.getTitle(),
ingo@312:             labels.getDomainAxisLabel(),
ingo@312:             null,
ingo@312:             null,
ingo@312:             true,
ingo@312:             false,
ingo@312:             false
ingo@312:         );
ingo@505: 
ingo@505:         XYPlot plot = (XYPlot) chart.getPlot();
ingo@505:         plot.setDomainAxis(0, new DateAxis(
ingo@505:             labels.getDomainAxisLabel(), TimeZone.getDefault(), locale));
ingo@312:     }
ingo@312: 
ingo@312: 
ingo@767:     /**
ingo@767:      * @see de.intevation.gnv.chart.AbstractXYLineChart#initData()
ingo@767:      */
ingo@298:     protected void initData() {
ingo@298:         log.debug("init data for timeseries chart");
ingo@298: 
ingo@298:         String  breakPoint1       = null;
ingo@298:         String  breakPoint2       = null;
ingo@298:         String  breakPoint3       = null;
ingo@298: 
ingo@298:         Iterator   iter       = resultSet.iterator();
ingo@298:         Result     row        = null;
ingo@298:         String     seriesName = null;
ingo@364:         String     parameter  = null;
ingo@298:         TimeSeries series     = null;
ingo@298: 
ingo@329:         int  idx       = 0;
ingo@329:         int  startPos  = 0;
ingo@329:         int  endPos    = 0;
ingo@329:         Date startDate = null;
ingo@329:         Date endDate   = null;
ingo@329: 
ingo@329:         Result[] results =
ingo@329:             (Result[]) resultSet.toArray(new Result[resultSet.size()]);
ingo@298: 
ingo@298:         while (iter.hasNext()) {
ingo@298:             row = (Result) iter.next();
ingo@298: 
ingo@298:             // add current data to plot and prepare for next one
ingo@298:             if (!row.getString("GROUP1").equals(breakPoint1) ||
ingo@298:                 !row.getString("GROUP2").equals(breakPoint2) ||
ingo@298:                 !row.getString("GROUP3").equals(breakPoint3)
ingo@298:             ) {
ingo@298:                 log.debug("prepare data/plot for next dataset");
ingo@298: 
ingo@298:                 if(series != null) {
ingo@329:                     // add gaps before adding series to chart
ingo@329:                     startDate = results[startPos].getDate("XORDINATE");
ingo@329:                     endDate   = results[endPos-1].getDate("XORDINATE");
ingo@329:                     addGaps(results,series,startDate,endDate,startPos,endPos);
ingo@364:                     addSeries(series, parameter, idx);
ingo@329: 
ingo@329:                     startPos  = endPos + 1;
ingo@298:                 }
ingo@298: 
ingo@298:                 // prepare variables for next plot
ingo@298:                 breakPoint1 = row.getString("GROUP1");
ingo@298:                 breakPoint2 = row.getString("GROUP2");
ingo@298:                 breakPoint3 = row.getString("GROUP3");
ingo@298: 
ingo@298:                 seriesName = createSeriesName(
ingo@298:                     breakPoint1,
ingo@298:                     breakPoint2,
ingo@298:                     breakPoint3
ingo@298:                 );
ingo@364:                 parameter = findParameter(seriesName);
ingo@298: 
ingo@298:                 log.debug("next dataset is '" + seriesName + "'");
ingo@298:                 series = new TimeSeries(seriesName, Minute.class);
ingo@298:             }
ingo@298: 
ingo@298:             addValue(row, series);
ingo@656:             storeMaxRange(ranges, row.getDouble("YORDINATE"), parameter);
ingo@329:             endPos++;
ingo@298:         }
ingo@298: 
ingo@496:         if (startPos < results.length && endPos-1 < results.length) {
ingo@496:             // add the last dataset if existing to plot and prepare its axis
ingo@496:             startDate = results[startPos].getDate("XORDINATE");
ingo@496:             endDate = results[endPos-1].getDate("XORDINATE");
ingo@496:             addGaps(results, series, startDate, endDate, startPos, endPos);
ingo@496:             addSeries(series, parameter, idx);
ingo@496:         }
ingo@310: 
ingo@334:         addDatasets();
ingo@298:     }
ingo@298: 
ingo@298: 
ingo@767:     /**
ingo@767:      * @see de.intevation.gnv.chart.AbstractXYLineChart#addValue(Result, Series)
ingo@767:      */
ingo@298:     protected void addValue(Result row, Series series) {
ingo@298:         ((TimeSeries) series).addOrUpdate(
ingo@298:             new Minute(row.getDate("XORDINATE")),
ingo@298:             row.getDouble("YORDINATE")
ingo@298:         );
ingo@298:     }
ingo@298: 
ingo@298: 
ingo@767:     /**
ingo@788:      * @param parameter
ingo@767:      * @see de.intevation.gnv.chart.AbstractXYLineChart#addSeries(Series,
ingo@767:      * String, int)
ingo@767:      */
ingo@364:     protected void addSeries(Series series, String parameter, int idx) {
ingo@364:         log.debug("add series (" + parameter + ")to timeseries chart");
ingo@298: 
ingo@298:         if (series == null) {
ingo@298:             log.warn("no data to add");
ingo@298:             return;
ingo@298:         }
ingo@298: 
ingo@334:         TimeSeriesCollection tsc = null;
ingo@334: 
ingo@334:         if (datasets.containsKey(parameter))
ingo@334:             tsc = (TimeSeriesCollection) datasets.get(parameter);
ingo@334:         else
ingo@334:             tsc = new TimeSeriesCollection();
ingo@334: 
ingo@334:         tsc.addSeries((TimeSeries) series);
ingo@334:         datasets.put(parameter, tsc);
ingo@334:     }
ingo@334: 
ingo@334: 
ingo@767:     /**
ingo@767:      * Method to add processed datasets to plot. Each dataset is adjusted using
ingo@767:      * <code>prepareAxis</code> and <code>adjustRenderer</code> methods.
ingo@767:      */
ingo@334:     protected void addDatasets() {
ingo@334:         Iterator   iter = parameters.iterator();
ingo@334:         XYPlot     plot = chart.getXYPlot();
ingo@334:         int        idx  = 0;
ingo@334: 
ingo@334:         TimeSeriesCollection tsc  = null;
ingo@334:         KeyValueDescibeData  data = null;
ingo@334:         String               key  = null;
ingo@334:         while (iter.hasNext()) {
ingo@334:             data = (KeyValueDescibeData) iter.next();
ingo@334:             key  = data.getValue();
ingo@334: 
ingo@334:             if (datasets.containsKey(key)) {
ingo@334:                 tsc  = (TimeSeriesCollection)datasets.get(key);
ingo@334:                 plot.setDataset(idx, tsc );
ingo@334:                 log.debug("Added " + key + " parameter to plot.");
ingo@334:                 prepareAxis(key, idx);
ingo@334:                 adjustRenderer(
ingo@334:                     idx++,
ingo@334:                     tsc.getSeriesCount(),
ingo@334:                     linesVisible,
ingo@334:                     shapesVisible
ingo@334:                 );
ingo@334:             }
ingo@334:         }
ingo@298:     }
ingo@298: 
ingo@298: 
ingo@767:     /**
ingo@788:      * @param locale
ingo@767:      * @see de.intevation.gnv.chart.AbstractXYLineChart#localizeDomainAxis(Axis,
ingo@767:      * Locale)
ingo@767:      */
ingo@315:     protected void localizeDomainAxis(Axis axis, Locale locale) {
ingo@526:         ((ValueAxis)axis).setStandardTickUnits(createStandardDateTickUnits(
ingo@526:             TimeZone.getDefault(),
ingo@526:             locale));
ingo@526:     }
ingo@526: 
ingo@526: 
ingo@767:     /**
ingo@788:      * @param zone
ingo@788:      * @param locale
ingo@788:      * @return TickUnitSource
ingo@767:      * @see org.jfree.chart.axis.DateAxis#createStandardDateTickUnits(TimeZone,
ingo@767:      * Locale)
ingo@767:      */
ingo@526:     public static TickUnitSource createStandardDateTickUnits(
ingo@526:         TimeZone zone,
ingo@526:         Locale locale)
ingo@526:     {
sascha@778:         /*
ingo@526:          * This method have been copied from JFreeChart's DateAxis class.
ingo@526:          * DateFormat objects are hard coded in DateAxis and cannot be adjusted.
ingo@526:          */
ingo@526:         if (zone == null) {
ingo@526:             throw new IllegalArgumentException("Null 'zone' argument.");
ingo@526:         }
ingo@526:         if (locale == null) {
ingo@526:             throw new IllegalArgumentException("Null 'locale' argument.");
ingo@526:         }
ingo@526:         TickUnits units = new TickUnits();
ingo@526: 
ingo@526:         // date formatters
ingo@526:         DateFormat f1 = new SimpleDateFormat("HH:mm:ss.SSS", locale);
ingo@526:         DateFormat f2 = new SimpleDateFormat("HH:mm:ss", locale);
ingo@526:         DateFormat f3 = new SimpleDateFormat("HH:mm", locale);
ingo@526:         DateFormat f4 = new SimpleDateFormat("d-MMM, HH:mm", locale);
ingo@526:         DateFormat f5 = new SimpleDateFormat("d-MMM yyyy", locale);
ingo@526:         DateFormat f6 = new SimpleDateFormat("MMM-yyyy", locale);
ingo@526:         DateFormat f7 = new SimpleDateFormat("yyyy", locale);
ingo@526: 
ingo@526:         f1.setTimeZone(zone);
ingo@526:         f2.setTimeZone(zone);
ingo@526:         f3.setTimeZone(zone);
ingo@526:         f4.setTimeZone(zone);
ingo@526:         f5.setTimeZone(zone);
ingo@526:         f6.setTimeZone(zone);
ingo@526:         f7.setTimeZone(zone);
ingo@526: 
ingo@526:         // milliseconds
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 1, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 5,
ingo@526:                 DateTickUnitType.MILLISECOND, 1, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 10,
ingo@526:                 DateTickUnitType.MILLISECOND, 1, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 25,
ingo@526:                 DateTickUnitType.MILLISECOND, 5, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 50,
ingo@526:                 DateTickUnitType.MILLISECOND, 10, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 100,
ingo@526:                 DateTickUnitType.MILLISECOND, 10, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 250,
ingo@526:                 DateTickUnitType.MILLISECOND, 10, f1));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MILLISECOND, 500,
ingo@526:                 DateTickUnitType.MILLISECOND, 50, f1));
ingo@526: 
ingo@526:         // seconds
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.SECOND, 1,
ingo@526:                 DateTickUnitType.MILLISECOND, 50, f2));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.SECOND, 5,
ingo@526:                 DateTickUnitType.SECOND, 1, f2));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.SECOND, 10,
ingo@526:                 DateTickUnitType.SECOND, 1, f2));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.SECOND, 30,
ingo@526:                 DateTickUnitType.SECOND, 5, f2));
ingo@526: 
ingo@526:         // minutes
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 1,
ingo@526:                 DateTickUnitType.SECOND, 5, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 2,
ingo@526:                 DateTickUnitType.SECOND, 10, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 5,
ingo@526:                 DateTickUnitType.MINUTE, 1, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 10,
ingo@526:                 DateTickUnitType.MINUTE, 1, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 15,
ingo@526:                 DateTickUnitType.MINUTE, 5, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 20,
ingo@526:                 DateTickUnitType.MINUTE, 5, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MINUTE, 30,
ingo@526:                 DateTickUnitType.MINUTE, 5, f3));
ingo@526: 
ingo@526:         // hours
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.HOUR, 1,
ingo@526:                 DateTickUnitType.MINUTE, 5, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.HOUR, 2,
ingo@526:                 DateTickUnitType.MINUTE, 10, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.HOUR, 4,
ingo@526:                 DateTickUnitType.MINUTE, 30, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.HOUR, 6,
ingo@526:                 DateTickUnitType.HOUR, 1, f3));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.HOUR, 12,
ingo@526:                 DateTickUnitType.HOUR, 1, f4));
ingo@526: 
ingo@526:         // days
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.DAY, 1,
ingo@526:                 DateTickUnitType.HOUR, 1, f5));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.DAY, 2,
ingo@526:                 DateTickUnitType.HOUR, 1, f5));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.DAY, 7,
ingo@526:                 DateTickUnitType.DAY, 1, f5));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.DAY, 15,
ingo@526:                 DateTickUnitType.DAY, 1, f5));
ingo@526: 
ingo@526:         // months
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MONTH, 1,
ingo@526:                 DateTickUnitType.DAY, 1, f6));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MONTH, 2,
ingo@526:                 DateTickUnitType.DAY, 1, f6));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MONTH, 3,
ingo@526:                 DateTickUnitType.MONTH, 1, f6));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MONTH, 4,
ingo@526:                 DateTickUnitType.MONTH, 1, f6));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.MONTH, 6,
ingo@526:                 DateTickUnitType.MONTH, 1, f6));
ingo@526: 
ingo@526:         // years
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 1,
ingo@526:                 DateTickUnitType.MONTH, 1, f7));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 2,
ingo@526:                 DateTickUnitType.MONTH, 3, f7));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 5,
ingo@526:                 DateTickUnitType.YEAR, 1, f7));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 10,
ingo@526:                 DateTickUnitType.YEAR, 1, f7));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 25,
ingo@526:                 DateTickUnitType.YEAR, 5, f7));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 50,
ingo@526:                 DateTickUnitType.YEAR, 10, f7));
ingo@526:         units.add(new DateTickUnit(DateTickUnitType.YEAR, 100,
ingo@526:                 DateTickUnitType.YEAR, 20, f7));
ingo@526: 
ingo@526:         return units;
ingo@315:     }
ingo@315: 
ingo@315: 
ingo@767:     /**
ingo@767:      * Method to get a message from resource bundle.
ingo@767:      *
ingo@788:      * @param locale Locale used to specify the resource bundle.
ingo@788:      * @param key Key to specify the required message.
ingo@788:      * @param def Default string if resource is not existing.
ingo@767:      *
ingo@767:      * @return Message
ingo@767:      */
ingo@350:     protected String getMessage(Locale locale, String key, String def) {
ingo@350:         return RessourceFactory.getInstance().getRessource(locale, key, def);
ingo@350:     }
ingo@350: 
ingo@350: 
ingo@767:     /**
ingo@767:      * @see de.intevation.gnv.chart.AbstractXYLineChart#createSeriesName(String,
ingo@767:      * String, String)
ingo@767:      */
ingo@298:     protected String createSeriesName(
ingo@298:         String breakPoint1,
ingo@298:         String breakPoint2,
ingo@298:         String breakPoint3
ingo@298:     ) {
ingo@298:         log.debug("create seriesname of timeseries chart");
ingo@298:         return findValueTitle(parameters, breakPoint1) +
ingo@298:             " " +
ingo@298:             findValueTitle(measurements, breakPoint2) +
ingo@298:             "m";
ingo@298:     }
ingo@329: 
ingo@329: 
ingo@767:     /**
sascha@778:      * Method to add gaps between two data points. The max valid space between
ingo@767:      * two data points is calculated by <code>calculateGapSize</code>.
ingo@767:      *
ingo@767:      * @param results All data points in this dataset.
ingo@767:      * @param series Series to be processed.
ingo@767:      * @param startDate Date item where the scan for gaps should begin.
ingo@767:      * @param endDate Date item where the scan should end.
ingo@767:      * @param startPos Start position of this series in <code>results</code>.
ingo@767:      * @param endPos End position of a series in <code>results</code>
ingo@767:      */
ingo@329:     protected void addGaps(
ingo@329:         Result[] results,
ingo@329:         Series   series,
ingo@329:         Date     startDate,
ingo@329:         Date     endDate,
ingo@329:         int      startPos,
ingo@329:         int      endPos
ingo@329:     ) {
ingo@329:         int  gapID   = results[startPos].getInteger("GAPID");
ingo@329:         long maxDiff = calculateGapSize(
ingo@329:             startDate, endDate, startPos, endPos, gapID
ingo@329:         );
ingo@329: 
ingo@642:         if (log.isDebugEnabled()) {
ingo@642:             log.debug("*****************************************************");
ingo@642:             log.debug("Values of gap detection.");
ingo@642:             log.debug("Start date: " + startDate.toString());
ingo@642:             log.debug("End date: " + endDate.toString());
ingo@642:             long diff = endDate.getTime() - startDate.getTime();
ingo@642:             log.debug("Time difference (in ms): " + diff);
ingo@642:             log.debug("Time difference (in h): " + (diff/(1000*60*60)));
ingo@642:             log.debug("Configured gap size (in %): " + GAP_SIZE);
ingo@642:             log.debug("Calculated gap size (in ms): " + maxDiff);
ingo@642:             log.debug("Calculated gap size (in h): " + (maxDiff/(1000*60*60)));
ingo@642:             log.debug("*****************************************************");
ingo@642:         }
ingo@642: 
ingo@329:         Date last = startDate;
ingo@329:         for (int i = startPos+1; i < endPos; i++) {
ingo@329:             Result res = results[i];
ingo@329:             Date   now = res.getDate("XORDINATE");
ingo@329: 
ingo@329:             if ((now.getTime() - last.getTime()) > maxDiff) {
ingo@329:                 // add gap, add 1 minute to last date and add null value
ingo@329:                 log.info(
ingo@329:                     "Gap between " +
ingo@329:                     last.toString() + " and " + now.toString()
ingo@329:                 );
ingo@329:                 last.setTime(last.getTime() + 60000);
ingo@329:                 ((TimeSeries) series).addOrUpdate(new Minute(last), null);
ingo@329:             }
ingo@329: 
ingo@329:             last = now;
ingo@329:         }
ingo@329:     }
ingo@329: 
ingo@329: 
ingo@767:     /**
ingo@767:      * Method to calculate the max space between two data points.
ingo@767:      *
ingo@767:      * @param start First date
ingo@767:      * @param end Last date
ingo@767:      * @param startPos Start position of the current series in the collection
ingo@767:      * containing the bunch of series.
ingo@767:      * @param endPos End position of the current series in the collection
ingo@767:      * containing the bunch of series.
ingo@767:      * @param gapID Gap id used to specify the time intervals.
ingo@767:      *
ingo@767:      * @return Min size of a gap.
ingo@767:      */
ingo@329:     protected long calculateGapSize(
ingo@329:         Date start,
ingo@329:         Date end,
ingo@329:         int  startPos,
ingo@329:         int  endPos,
ingo@329:         int  gapID
ingo@329:     ){
ingo@642:         long maxGap   = (end.getTime() - start.getTime()) / 100 * GAP_SIZE;
ingo@329:         long interval = getTimeGapValue(start, end, startPos, endPos, gapID);
ingo@329: 
ingo@329:         if (maxGap < interval)
ingo@329:             maxGap = interval + 10;
ingo@329: 
ingo@329:         return maxGap;
ingo@329:     }
ingo@329: 
ingo@329: 
ingo@767:     /**
ingo@767:      * Determine the interval size between two data points.
ingo@767:      *
ingo@767:      * @param dStart Start date
ingo@767:      * @param dEnd End date
sascha@778:      * @param pStart Index of start point in series used to specify the total
ingo@767:      * amount of date items.
ingo@767:      * @param pEnd Index of end point in series used to specify the total amount
ingo@767:      * of date items.
ingo@767:      * @param gapID Gap id used to determine gaps configured in a xml document.
ingo@767:      *
ingo@767:      * @return Interval size between two data points.
ingo@767:      */
ingo@329:     protected long getTimeGapValue(
ingo@329:         Date dStart,
ingo@329:         Date dEnd,
ingo@329:         int  pStart,
ingo@329:         int  pEnd,
ingo@329:         int  gapID
ingo@329:     ){
ingo@329:         long gap = 0;
ingo@329: 
ingo@329:         if (gapID < 0 || gapID >= 99) {
ingo@329: 
ingo@329:             if (gapID == -1) {
ingo@329:                 // no gaps in meshes
ingo@329:                 gap = NO_TIME_GAP;
ingo@329:             }
ingo@329:             else if (pEnd-pStart < 60) {
ingo@329:                 gap = (3/(pEnd-pStart)) * (dEnd.getTime() - dStart.getTime());
ingo@329:             }
ingo@329:         }
ingo@329:         else{
ingo@329:             Iterator it = timeGaps.iterator();
ingo@329: 
ingo@329:             while (it.hasNext()) {
ingo@329:                 TimeGap tempTimeGap = (TimeGap) it.next();
ingo@329: 
ingo@329:                 if (tempTimeGap.getKey() == gapID){
ingo@329:                     String unit     = tempTimeGap.getUnit();
ingo@329:                     int    gapValue = tempTimeGap.getValue();
ingo@329: 
ingo@329:                     if (unit.equals(TimeGap.TIME_UNIT_MINUTE)) {
ingo@329:                         gap = gapValue * TimeGap.MINUTE_IN_MILLIS;
ingo@329:                     }
ingo@329:                     else if (unit.equals(TimeGap.TIME_UNIT_HOUR)) {
ingo@329:                         gap = gapValue * TimeGap.HOUR_IN_MILLIS;
ingo@329:                     }
ingo@329:                     else if (unit.equals(TimeGap.TIME_UNIT_DAY)) {
ingo@329:                         gap = gapValue * TimeGap.DAY_IN_MILLIS;
ingo@329:                     }
ingo@329:                     else if (unit.equals(TimeGap.TIME_UNIT_WEEK)) {
ingo@329:                         gap = gapValue * TimeGap.WEEK_IN_MILLIS;
ingo@329:                     }
ingo@329:                     else if (unit.equals(TimeGap.TIME_UNIT_MONTH)) {
ingo@329:                         gap = gapValue * (TimeGap.DAY_IN_MILLIS *30);
ingo@329:                     }
ingo@329:                     else if (unit.equals(TimeGap.TIME_UNIT_YEAR)) {
ingo@329:                         gap = gapValue * (TimeGap.DAY_IN_MILLIS *365);
ingo@329:                     }
ingo@329:                     break;
ingo@329:                 }
ingo@329:             }
ingo@329:         }
ingo@329: 
ingo@329:         return gap;
ingo@329:     }
ingo@298: }
ingo@315: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :