ingo@1115: /* ingo@1115: * Copyright (c) 2010 by Intevation GmbH ingo@1115: * ingo@1115: * This program is free software under the LGPL (>=v2.1) ingo@1115: * Read the file LGPL.txt coming with the software for details ingo@1115: * or visit http://www.gnu.org/licenses/ if it does not exist. ingo@1115: */ ingo@1115: tim@335: package de.intevation.gnv.state.timeseries; tim@335: sascha@480: import java.io.IOException; tim@335: import java.io.OutputStream; tim@335: import java.io.UnsupportedEncodingException; ingo@839: ingo@839: import java.text.DateFormat; ingo@851: import java.text.NumberFormat; ingo@839: import java.text.ParseException; ingo@839: tim@335: import java.util.ArrayList; tim@335: import java.util.Collection; ingo@839: import java.util.Date; ingo@629: import java.util.HashMap; tim@335: import java.util.Iterator; tim@335: import java.util.List; tim@335: import java.util.Locale; ingo@629: import java.util.Map; tim@335: import java.util.Vector; tim@335: tim@335: import org.apache.log4j.Logger; sascha@480: import org.jfree.chart.ChartTheme; tim@335: import org.w3c.dom.Document; tim@335: import org.w3c.dom.Element; tim@335: import org.w3c.dom.Node; tim@335: import org.w3c.dom.NodeList; tim@335: tim@812: import au.com.bytecode.opencsv.CSVWriter; sascha@1117: import de.intevation.artifacts.common.utils.Config; sascha@1117: import de.intevation.artifacts.common.utils.XMLUtils; tim@812: import de.intevation.artifacts.ArtifactNamespaceContext; tim@812: import de.intevation.artifacts.CallContext; tim@812: import de.intevation.artifacts.CallMeta; tim@812: import de.intevation.gnv.artifacts.context.GNVArtifactContext; tim@812: import de.intevation.gnv.artifacts.ressource.RessourceFactory; tim@812: import de.intevation.gnv.chart.Chart; tim@812: import de.intevation.gnv.chart.ChartLabels; tim@812: import de.intevation.gnv.chart.DefaultHistogram; tim@812: import de.intevation.gnv.chart.TimeSeriesChart; tim@812: import de.intevation.gnv.chart.XMLChartTheme; tim@812: import de.intevation.gnv.chart.exception.TechnicalChartException; tim@812: import de.intevation.gnv.exports.ChartExportHelper; tim@812: import de.intevation.gnv.exports.DefaultDataCollector; tim@812: import de.intevation.gnv.exports.DefaultExport; tim@812: import de.intevation.gnv.exports.DefaultProfile; tim@812: import de.intevation.gnv.exports.Export; tim@812: import de.intevation.gnv.exports.ODVExport; tim@812: import de.intevation.gnv.exports.SimpleOdvDataCollector; tim@812: import de.intevation.gnv.exports.Export.Profile; tim@812: import de.intevation.gnv.geobackend.base.Result; tim@812: import de.intevation.gnv.histogram.HistogramHelper; tim@812: import de.intevation.gnv.state.InputData; tim@812: import de.intevation.gnv.state.OutputStateBase; ingo@838: import de.intevation.gnv.state.State; tim@812: import de.intevation.gnv.state.describedata.DefaultKeyValueDescribeData; tim@812: import de.intevation.gnv.state.describedata.KeyValueDescibeData; tim@812: import de.intevation.gnv.state.describedata.MinMaxDescribeData; tim@812: import de.intevation.gnv.state.describedata.NamedArrayList; tim@812: import de.intevation.gnv.state.describedata.NamedCollection; tim@812: import de.intevation.gnv.state.exception.StateException; tim@812: import de.intevation.gnv.statistics.Statistic; tim@812: import de.intevation.gnv.statistics.StatisticSet; tim@812: import de.intevation.gnv.statistics.Statistics; tim@812: import de.intevation.gnv.statistics.TimeseriesStatistics; tim@812: import de.intevation.gnv.statistics.exception.StatisticsException; tim@812: import de.intevation.gnv.timeseries.gap.DefaultTimeGap; tim@812: import de.intevation.gnv.timeseries.gap.TimeGap; tim@812: import de.intevation.gnv.utils.ArtifactXMLUtilities; tim@812: tim@335: /** sascha@780: * @author Tim Englich sascha@780: * @author Ingo Weinzierl tim@335: */ tim@335: public class TimeSeriesOutputState extends OutputStateBase { tim@335: ingo@804: /** ingo@804: * A boolean property to enable caching of charts after they have been ingo@804: * created. This property can be adjusted via system property 'cache.chart'. ingo@804: * 'true' of 'false' are valid options - defaults to 'false'.
ingo@804: * NOTE: If chart caching is enabled, charts aren't sensible to ingo@804: * changes in the parameterization anymore, after a first chart has been ingo@804: * created for an artifact. ingo@804: */ tim@335: protected static final boolean CACHE_CHART = tim@335: Boolean.parseBoolean(System.getProperty("cache.chart", "false")); tim@335: ingo@804: /** ingo@804: * Supported image export formats. ingo@804: */ tim@335: protected static final String[] IMG_EXPORT_FORMAT = { tim@335: "PNG", "JPEG", "GIF" tim@335: }; tim@335: tim@335: /** tim@335: * The UID of this Class tim@335: */ tim@335: private static final long serialVersionUID = 4178407570503098858L; tim@335: tim@335: /** tim@335: * the logger, used to log exceptions and additonaly information tim@335: */ tim@335: private static Logger log = Logger tim@335: .getLogger(TimeSeriesOutputState.class); tim@335: ingo@1040: protected List timeGapDefinitions = null; tim@335: ingo@804: /** ingo@804: * Key in resource bundle the x-axis title is stored. ingo@804: */ ingo@342: protected String domainLable = "chart.timeseries.title.xaxis"; tim@335: tim@335: protected String featureValuesName = "featureid"; tim@335: protected String parameterValuesName = "parameterid"; tim@335: protected String measuremenValueName = "measurementid"; tim@335: protected String dateValueName = "dateid"; tim@812: protected String timeIntervalValueName = "timeinterval"; ingo@368: ingo@804: ingo@804: /** ingo@804: * Array used to specify the columns used in csv exports. ingo@804: */ ingo@368: public static final String [] TIMESERIES_CSV_PROFILE_COLUMNS = { tim@335: "XORDINATE", tim@335: "YORDINATE", tim@335: "GROUP1", tim@335: "GROUP2", tim@335: "GROUP3" tim@335: }; ingo@368: ingo@368: ingo@804: /** ingo@804: * Column labels used in csv exports. ingo@804: */ ingo@368: public static final String [] TIMESERIES_TIMESERIES_CSV_COLUMN_LABEL = { ingo@368: "Date/Time", ingo@368: "Value", ingo@368: "ParameterID", ingo@368: "MeasurementID", ingo@368: "TimeseriesID" ingo@368: }; ingo@368: ingo@804: ingo@804: /** ingo@804: * Array used to specify the column in csv exports on meshes. ingo@804: */ ingo@368: public static final String [] TIMESERIES_MESH_CSV_COLUMN_LABEL = { ingo@368: "Date/Time", ingo@368: "Value", ingo@368: "ParameterID", ingo@368: "FeatureID", ingo@368: "MeshID" ingo@368: }; ingo@368: ingo@804: ingo@804: /** ingo@804: * Array used to specify the columns in odv exports. ingo@804: */ tim@335: public static final String [] TIMESERIES_ODV_PROFILE_NAMES = { tim@335: "CRUISE", tim@335: "STATION", tim@335: "TYPE", tim@765: "TIMEVALUE", tim@335: "SHAPE", tim@335: "BOTDEPTH", tim@335: "DEPTH", tim@765: "QF" tim@335: }; tim@335: tim@335: ingo@804: /** ingo@804: * Column labels used in odv exports. ingo@804: */ tim@335: public static final String [] ODV_COLUMN_HEADER = { tim@335: "Cruise", tim@335: "Station", tim@335: "Type", tim@765: "yyyy-mm-dd hh:mm", tim@765: "Lon (°E)", tim@765: "Lat (°N)", tim@335: "Bot. Depth [m]", tim@335: "Depth [m]", tim@765: "QF" tim@335: }; tim@335: sascha@805: tim@335: /** tim@335: * Profile for exporting data to odv tim@335: */ tim@335: public static final Profile TIMESERIES_ODV_PROFILE = tim@335: new DefaultProfile( tim@335: ODV_COLUMN_HEADER, tim@335: '\t', tim@335: CSVWriter.NO_QUOTE_CHARACTER, tim@335: CSVWriter.NO_ESCAPE_CHARACTER, tim@335: "ODV", tim@335: "ISO-8859-1"); tim@335: ingo@839: tim@335: /** tim@335: * Constructor tim@335: */ tim@335: public TimeSeriesOutputState() { tim@335: super(); tim@335: } tim@335: ingo@536: ingo@804: /** ingo@804: * Calls getChartResult which puts the data used for chart ingo@804: * creation into cache. This redruces waiting periods after selecting an ingo@804: * output type. ingo@804: * ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @param context The CallContext object. ingo@804: * @throws StateException if an error occured while fetching the data used ingo@804: * for chart creation. ingo@804: */ ingo@536: @Override ingo@536: public void initialize(String uuid, CallContext context) ingo@536: throws StateException ingo@536: { ingo@536: getChartResult(uuid, context); ingo@536: } ingo@536: ingo@536: tim@335: /** ingo@804: * This out target has following modes:
ingo@804: *
    ingo@804: *
  1. chart: Creates a chart displaying the data corresponding to the ingo@804: * current parameterization. A chart has following export modes:
    ingo@804: *
      ingo@804: *
    1. img: Image representation of a chart.
    2. ingo@804: *
    3. pdf: PDF representation of a chart.
    4. ingo@804: *
    5. svg: SVG representation of a chart.
    6. ingo@804: *
    ingo@804: * All selected parameters are drawn into a single chart.
    ingo@804: *
  2. ingo@804: *
  3. histogram: Creates a histogram displaying the data corresponding to ingo@804: * the current parameterization. A histogram has following export modes:
    ingo@804: *
      ingo@804: *
    1. img: Image representation of a histogram.
    2. ingo@804: *
    3. pdf: PDF representation of a histogram.
    4. ingo@804: *
    5. svg: SVG representation of a histogram.
    6. ingo@804: *
    ingo@804: * A single histogram is created for each selected parameter.
    ingo@804: *
  4. ingo@804: *
  5. statistic: Creates a statistic with important figures.
  6. ingo@804: *
  7. csv: Creates a csv file.
  8. ingo@804: *
  9. odv: Creates a odv file.
  10. ingo@804: *
ingo@804: * ingo@804: * @param format Document which contains some export specific information. ingo@804: * @param inputData Contains some meta information to adjust the export ingo@804: * (e.g. width and height of a chart). ingo@804: * @param outputStream The output stream used to return the export. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @param callContext The CallContext object. ingo@804: * @throws StateException if an error occured while creating the export ingo@804: * object. tim@335: */ tim@335: public void out( sascha@480: Document format, tim@335: Collection inputData, sascha@480: OutputStream outputStream, sascha@480: String uuid, sascha@480: CallContext callContext tim@335: ) throws StateException tim@335: { tim@335: log.debug("TimeSeriesOutputTransition.out"); tim@335: ingo@467: String outputMode = XMLUtils.xpathString( ingo@467: format, XPATH_OUTPUT_MODE, ArtifactNamespaceContext.INSTANCE); ingo@467: ingo@639: String mode = XMLUtils.xpathString( ingo@639: format, XPATH_EXPORT_MODE, ArtifactNamespaceContext.INSTANCE); ingo@639: ingo@639: if (mode == null || mode.equals("")) { ingo@639: mode = "img"; ingo@639: } ingo@639: ingo@467: String mimeType = XMLUtils.xpathString( ingo@467: format, XPATH_MIME_TYPE, ArtifactNamespaceContext.INSTANCE); tim@335: ingo@358: CallMeta callMeta = callContext.getMeta(); ingo@358: ingo@502: int chartWidth = 600; ingo@502: int chartHeight = 400; ingo@502: boolean sVisible = false; ingo@629: int binCount = 0; ingo@629: int binWidth = 0; ingo@629: ingo@629: Map requestParameter = new HashMap(); ingo@502: ingo@502: // lines are always visible. if lines should be configurable we need a ingo@502: // parameter in the user interface ingo@502: boolean lVisible = true; ingo@502: ingo@502: try { ingo@502: if (inputData != null) { ingo@502: Iterator it = inputData.iterator(); ingo@502: while (it.hasNext()) { ingo@502: InputData ip = it.next(); ingo@502: String optionName = ip.getName().trim(); ingo@629: requestParameter.put(optionName, ip.getValue()); ingo@502: ingo@502: if (optionName.equals("width")) { ingo@502: chartWidth = Integer.parseInt(ip.getValue()); ingo@502: } ingo@502: else if (optionName.equals("height")) { ingo@502: chartHeight = Integer.parseInt(ip.getValue()); ingo@502: } ingo@502: else if (optionName.equals("points")) { ingo@502: sVisible = Boolean.parseBoolean(ip.getValue()); ingo@502: } ingo@502: } ingo@502: } ingo@502: } catch (NumberFormatException e) { ingo@725: log.warn(e, e); ingo@725: XMLUtils.toStream( ingo@725: feedFailure("not.a.number"), ingo@725: outputStream); ingo@725: return; ingo@502: } ingo@502: tim@335: try { ingo@639: Collection parameters = ingo@639: getParameters(uuid); ingo@639: Collection measurements = ingo@639: getMeasurements(uuid); ingo@639: Collection dates = ingo@639: getDates(uuid); ingo@639: ingo@639: Locale[] serverLocales = ingo@639: RessourceFactory.getInstance().getLocales(); ingo@639: Locale locale = ingo@639: callMeta.getPreferredLocale(serverLocales); ingo@639: ingo@639: ChartLabels chartLables = createChartLabels(locale, uuid); ingo@639: ingo@639: log.debug( ingo@639: "Best locale - regarding intersection of server and " + ingo@639: "browser locales - is " + locale.toString() ingo@639: ); ingo@639: ingo@639: String exportFormat = getExportFormat(mimeType); ingo@639: ingo@639: // CHART tim@335: if (outputMode.equalsIgnoreCase("chart")) { tim@335: log.debug("Chart will be generated."); tim@335: ingo@639: if (mode.equalsIgnoreCase("img")) { ingo@639: createChart( ingo@639: outputStream, ingo@639: parameters, ingo@639: measurements, ingo@639: dates, ingo@639: chartLables, ingo@639: callContext, ingo@639: uuid, ingo@639: exportFormat, ingo@639: locale, ingo@639: chartWidth, ingo@639: chartHeight, ingo@639: lVisible, ingo@639: sVisible, ingo@639: callContext ingo@639: ); ingo@639: } ingo@639: else if (mode.equalsIgnoreCase("pdf")) { ingo@1050: callContext.putContextValue("chart.width", chartWidth); ingo@1050: callContext.putContextValue("chart.height", chartHeight); ingo@1050: callContext.putContextValue("shapes.visible", sVisible); ingo@1050: callContext.putContextValue("lines.visible", lVisible); ingo@1050: callContext.putContextValue("locale", locale); ingo@1050: ingo@639: createPDF( ingo@639: outputStream, ingo@639: parameters, ingo@639: measurements, ingo@639: dates, ingo@639: chartLables, ingo@639: uuid, ingo@639: "A4", ingo@639: callContext ingo@639: ); ingo@639: } ingo@639: else if (mode.equalsIgnoreCase("svg")) { ingo@639: createSVG( ingo@639: outputStream, ingo@639: getParameters(uuid), ingo@639: getMeasurements(uuid), ingo@639: getDates(uuid), ingo@639: createChartLabels(locale, uuid), ingo@639: uuid, ingo@639: locale, ingo@639: chartWidth, ingo@639: chartHeight, ingo@639: lVisible, ingo@639: sVisible, ingo@639: callContext ingo@639: ); ingo@639: } ingo@639: } ingo@639: // HISTOGRAM ingo@639: else if (outputMode.equalsIgnoreCase("histogram")) { ingo@639: requestParameter.put("locale", locale); tim@335: ingo@1080: Chart[] histograms = getHistograms( ingo@1080: uuid, ingo@1080: callContext, ingo@1080: parameters, ingo@1080: measurements, ingo@1080: dates, ingo@1080: requestParameter); ingo@617: ingo@639: if (mode.equalsIgnoreCase("img")) { ingo@639: ChartExportHelper.exportHistograms( ingo@639: outputStream, ingo@639: histograms, ingo@639: exportFormat, ingo@639: chartWidth, ingo@639: chartHeight ingo@639: ); ingo@639: } ingo@639: else if (mode.equalsIgnoreCase("pdf")) { ingo@1053: callContext.putContextValue("chart.width", chartWidth); ingo@1053: callContext.putContextValue("chart.height", chartHeight); ingo@1053: ingo@640: ChartExportHelper.exportHistogramsAsPDF( ingo@640: outputStream, ingo@640: histograms, ingo@640: "A4", ingo@1053: 50F, 50F, 50F, 50F, ingo@1053: callContext ingo@640: ); ingo@639: } ingo@639: else if (mode.equalsIgnoreCase("svg")) { ingo@639: ChartExportHelper.exportHistogramsAsSVG( ingo@639: outputStream, ingo@639: histograms, ingo@639: null, ingo@639: chartWidth, ingo@639: chartHeight ingo@639: ); ingo@639: } tim@335: } tim@335: else if (outputMode.equalsIgnoreCase("csv")) { tim@335: log.debug("CSV-File will be generated."); sascha@452: Object result = getChartResult(uuid, callContext); sascha@452: if (result instanceof Collection) { sascha@452: this.createCSV( sascha@452: outputStream, sascha@452: (Collection)result); sascha@452: } tim@335: } else if (outputMode.equalsIgnoreCase("statistics")) { tim@335: log.debug("Statistics will be generated."); sascha@446: sascha@446: Collection statistics; sascha@446: sascha@454: Statistics s = getStatisticsGenerator(); sascha@454: Object result = getChartResult(uuid, callContext); sascha@446: sascha@454: if (result != null && s != null) { sascha@446: statistics = s.calculateStatistics( sascha@454: result, sascha@446: parameters, sascha@446: measurements, sascha@446: dates); sascha@446: } sascha@446: else { sascha@446: statistics = new ArrayList(); sascha@446: } sascha@446: ingo@851: Document doc = writeStatistics2XML(statistics, locale); sascha@454: sascha@481: XMLUtils.toStream(doc, outputStream); sascha@454: ingo@617: } ingo@617: else if (outputMode.equalsIgnoreCase("odv")) { tim@830: createODV(outputStream, uuid, callContext); tim@335: } tim@335: } catch (IOException e) { tim@335: log.error(e, e); tim@335: throw new StateException(e); tim@335: } catch (TechnicalChartException e) { tim@335: log.error(e, e); tim@335: throw new StateException(e); tim@335: } catch (StatisticsException e) { tim@335: log.error(e, e); tim@335: throw new StateException(e); tim@335: } tim@335: } tim@335: tim@335: ingo@1080: protected Chart[] getHistograms( ingo@1080: String uuid, ingo@1080: CallContext callContext, ingo@1080: Collection parameters, ingo@1080: Collection measurements, ingo@1080: Collection dates, ingo@1080: Map requestParameter ingo@1080: ) { ingo@1080: Locale locale = (Locale) requestParameter.get("locale"); ingo@1080: ingo@1080: Collection results = (Collection) getChartResult(uuid, callContext); ingo@1080: ChartTheme theme = createStyle(callContext); ingo@1080: ingo@1080: Object[][] data = HistogramHelper.prepareHistogramData( ingo@1080: results, parameters, measurements, dates); ingo@1080: ingo@1080: int size = data.length; ingo@1080: Chart[] histograms = new Chart[size]; ingo@1080: ingo@1080: for (int i = 0; i < size; i++) { ingo@1080: ChartLabels labels = HistogramHelper.createHistogramLabels( ingo@1080: uuid, callContext, locale, data[i]); ingo@1080: ingo@1080: histograms[i] = new DefaultHistogram( ingo@1080: labels, data[i], theme, requestParameter); ingo@1080: } ingo@1080: ingo@1080: return histograms; ingo@1080: } ingo@1080: ingo@804: /** tim@830: * @param outputStream tim@830: * @param uuid tim@830: * @throws IOException tim@830: * @throws StateException tim@830: */ sascha@835: protected void createODV(OutputStream outputStream, sascha@835: String uuid, tim@830: CallContext callContext) tim@830: throws IOException, tim@830: StateException { tim@830: Collection odvResult = this.getODVResult(uuid); tim@830: this.createODV(outputStream, odvResult,uuid); tim@830: } tim@830: tim@830: tim@830: /** ingo@804: * Retrieves the export format (e.g. png, gif, jpeg). ingo@804: * ingo@804: * @param mime Export format specified by the incoming request. ingo@804: * @return mime if it is supported - otherwise the first format in ingo@804: * {@link #IMG_EXPORT_FORMAT}. ingo@804: */ tim@335: protected String getExportFormat(String mime) { tim@335: for(int i = 0; i < IMG_EXPORT_FORMAT.length; i++) { tim@335: if (mime.trim().toUpperCase().indexOf(IMG_EXPORT_FORMAT[i]) > 0) tim@335: return IMG_EXPORT_FORMAT[i]; tim@335: } tim@335: tim@335: // no format found relating to mimeType, default export as PNG tim@335: return IMG_EXPORT_FORMAT[0]; tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Returns a collection containing all selected KeyValueDescibeData objects ingo@804: * of parameters. ingo@804: * ingo@804: * @param parameters A collection with KeyValueDescibeData objects. ingo@804: * @return a collection cleaned from unselected objects. ingo@804: * @deprecated ingo@804: */ tim@335: protected Collection getCleanedParameters(Collection parameters) { tim@335: Iterator iter = parameters.iterator(); tim@335: Collection parameter = new Vector(parameters); tim@335: while (iter.hasNext()) { tim@335: KeyValueDescibeData data = (KeyValueDescibeData)iter.next(); tim@335: if (!data.isSelected()) tim@335: parameter.remove(data); tim@335: } tim@335: tim@335: return parameter; tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Calls {@link #getCleanedParameters(java.util.Collection)} with the ingo@804: * collection returned by {@link #getParameters(java.lang.String)}. ingo@804: * ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return a cleaned collection. ingo@804: */ tim@335: protected Collection getCleanedParameters(String uuid) { tim@335: return getCleanedParameters(getParameters(uuid)); tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Create a csv file representing the data corresponding to the current ingo@804: * parameterization. ingo@804: * ingo@804: * @param out The output stream used to export the csv file. ingo@804: * @param results The data used for csv file creation. ingo@804: * @throws UnsupportedEncodingException if the encoding is not supported. ingo@804: * @throws IOException if an error occured while writing to output stream. ingo@804: * @throws StateException if an error occured while creating the csv file. ingo@804: */ ingo@368: protected void createCSV(OutputStream out, Collection results) ingo@368: throws UnsupportedEncodingException, IOException, StateException ingo@368: { ingo@368: Iterator iter = results.iterator(); ingo@368: Result res = iter.hasNext() ? (Result) iter.next() : null; tim@335: ingo@368: if (res == null) ingo@368: return; ingo@368: ingo@368: Profile profile = null; ingo@368: int dataid = res.getInteger("DATAID").intValue(); ingo@368: ingo@368: // on meshes ingo@368: if (dataid == 2) { ingo@368: profile = new DefaultProfile( ingo@368: TIMESERIES_MESH_CSV_COLUMN_LABEL, ingo@368: ',', ingo@368: '"', ingo@368: '"', ingo@368: "CSV", ingo@368: "ISO-8859-1"); ingo@368: } ingo@368: ingo@368: // on timeseries ingo@368: else { ingo@368: profile = new DefaultProfile( ingo@368: TIMESERIES_TIMESERIES_CSV_COLUMN_LABEL, ingo@368: ',', ingo@368: '"', ingo@368: '"', ingo@368: "CSV", ingo@368: "ISO-8859-1"); ingo@368: } ingo@368: ingo@368: DefaultExport export = new DefaultExport( ingo@368: new DefaultDataCollector(TIMESERIES_CSV_PROFILE_COLUMNS)); ingo@368: export.create(profile, out, results); tim@335: } tim@335: ingo@368: tim@335: /** ingo@804: * Create an odv file representing the data corresponding to the current ingo@804: * parameterization. sascha@805: * ingo@804: * @param outputStream The output stream used to export the odv file. ingo@804: * @param result The data used for odv file creation. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @throws IOException if an error occured while writing to output stream. ingo@804: * @throws StateException if an error occured while creating the odv file. tim@335: */ sascha@778: protected void createODV(OutputStream outputStream, tim@765: Collection result, tim@765: String uuid) tim@335: throws IOException, StateException { tim@335: tim@765: Export export = new ODVExport(new SimpleOdvDataCollector( tim@765: TIMESERIES_ODV_PROFILE_NAMES), tim@812: this.getParameters(uuid), tim@812: this.getStartTime()); tim@335: tim@335: if (result == null) tim@335: log.error("#################### RESULT == NULL #################"); tim@335: export.create(TIMESERIES_ODV_PROFILE, outputStream, result); tim@335: } tim@335: tim@335: /** tim@812: * Method that returns the Starttime of an TimeSeries or tim@812: * null if it is not a TimeSeries. tim@812: * @return the Starttime of an TimeSeries or null if it is not a TimeSeries. tim@812: */ tim@812: protected String getStartTime(){ tim@812: InputData data = inputData.get(this.timeIntervalValueName); tim@812: if (data != null){ tim@812: Object describeData = data.getObject(); tim@812: if (describeData instanceof MinMaxDescribeData){ tim@816: String value = ((MinMaxDescribeData)describeData) tim@816: .getMinValue().toString(); tim@816: return value.substring(0,value.lastIndexOf(':')); tim@812: } tim@812: return null; tim@812: }else{ tim@812: return null; tim@812: } sascha@835: tim@812: } tim@812: tim@812: /** ingo@804: * Returns the statistic generator. ingo@804: * ingo@804: * @return the statistic generator. tim@335: */ tim@335: protected Statistics getStatisticsGenerator() { tim@335: Statistics s = new TimeseriesStatistics(); tim@335: return s; tim@335: } tim@335: ingo@804: ingo@804: /** ingo@804: * Writes the statistic into an xml document. ingo@804: * ingo@804: * @param statistic Statistic to be written to xml document. ingo@804: * @return the xml document containing the statistic. ingo@804: */ ingo@851: protected Document writeStatistics2XML( ingo@851: Collection statistic, Locale locale) ingo@851: { tim@335: ArtifactXMLUtilities xmlUtilities = new ArtifactXMLUtilities(); tim@335: Document doc = XMLUtils.newDocument(); ingo@851: ingo@851: NumberFormat format = NumberFormat.getInstance(locale); ingo@851: tim@335: if (statistic != null) { ingo@804: Node statisticResults = ArtifactXMLUtilities.createArtifactElement(doc, tim@335: "statistics"); tim@335: doc.appendChild(statisticResults); tim@335: Iterator it = statistic.iterator(); tim@335: while (it.hasNext()) { tim@335: StatisticSet set = it.next(); ingo@804: Element setElement = ArtifactXMLUtilities.createArtifactElement(doc, tim@335: "statistic"); tim@335: setElement.setAttribute("name", set.getName()); sascha@778: tim@335: Iterator sit = set.getStatistics().iterator(); tim@335: while (sit.hasNext()){ tim@335: Statistic s = sit.next(); ingo@804: Element result = ArtifactXMLUtilities.createArtifactElement(doc, tim@335: "statistic-value"); tim@335: result.setAttribute("name", s.getKey()); ingo@851: result.setAttribute("value", format.format(s.getValue())); tim@335: setElement.appendChild(result); tim@335: } tim@335: statisticResults.appendChild(setElement); tim@335: } tim@335: tim@335: } tim@335: return doc; tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Returns the name of the selected feature. ingo@804: * ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return the name of the selectef feature. ingo@804: */ tim@335: protected String getSelectedFeatureName(String uuid) { tim@335: Collection values = getCollection(featureValuesName, uuid); tim@335: tim@335: if (values != null) { tim@335: Iterator it = values.iterator(); tim@335: tim@335: while (it.hasNext()) { tim@335: KeyValueDescibeData data = (KeyValueDescibeData) it.next(); ingo@610: return data.getValue(); ingo@610: } tim@335: ingo@610: return ""; tim@335: } ingo@610: return ""; tim@335: } tim@335: tim@335: tim@335: /** ingo@804: * Creates a chart and writes it to outputStream. ingo@804: * ingo@804: * @param outputStream The stream used to write the chart to. ingo@804: * @param parameters A collection with parameters. ingo@804: * @param measurements A collection with measurements. ingo@804: * @param dates A collection with dates. ingo@804: * @param chartLables The labels used to decorate the chart. ingo@804: * @param context The CallContext. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @param exportFormat The format the chart used to be exported as. ingo@804: * @param locale The Locale to specify the language. ingo@804: * @param width The width of the chart. ingo@804: * @param height The height of the chart. ingo@804: * @param linesVisible A boolean property to determine the visibility of ingo@804: * lines connecting two datapoins. ingo@804: * @param shapesVisible A boolean property to determine the visibility of ingo@804: * datapoints. ingo@804: * @param callContext The CallContext object. ingo@804: * @throws IOException if an error occured while writing to output stream. ingo@804: * @throws TechnicalChartException if an error occured while chart creation. tim@335: */ tim@335: protected void createChart( tim@335: OutputStream outputStream, tim@335: Collection parameters, tim@335: Collection measurements, tim@335: Collection dates, tim@335: ChartLabels chartLables, ingo@358: CallContext context, tim@335: String uuid, tim@335: String exportFormat, tim@335: Locale locale, tim@335: int width, tim@335: int height, tim@335: boolean linesVisible, sascha@439: boolean shapesVisible, sascha@439: CallContext callContext tim@335: ) tim@335: throws IOException, TechnicalChartException tim@335: { tim@335: log.debug("Create chart."); tim@335: Chart chart = getChart( tim@335: chartLables, ingo@358: createStyle(context), tim@335: parameters, tim@335: measurements, tim@335: dates, sascha@439: getChartResult(uuid, callContext), tim@335: locale, // Locale tim@335: uuid, tim@335: linesVisible, sascha@439: shapesVisible, sascha@439: callContext tim@335: ); tim@335: tim@335: if (chart == null) { tim@335: log.error("Could not initialize chart."); tim@335: return; tim@335: } tim@335: tim@335: log.debug( tim@335: "export chart as " + exportFormat + tim@335: " in " + width + "x" + height tim@335: ); tim@335: tim@335: ChartExportHelper.exportImage( tim@335: outputStream, tim@335: chart.generateChart(), tim@335: exportFormat, tim@335: width, tim@335: height tim@335: ); tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Creates a chart and writes it as pdf to outputStream. ingo@804: * ingo@804: * @param outputStream The stream used to write the chart to. ingo@804: * @param parameters A collection with parameters. ingo@804: * @param measurements A collection with measurements. ingo@804: * @param dates A collection with dates. ingo@804: * @param chartLables The labels used to decorate the chart. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @param exportFormat The format the chart used to be exported as. ingo@804: * @param landscape A boolean property to determine the alignment of the ingo@804: * chart. ingo@804: * @param linesVisible A boolean property to determine the visibility of ingo@804: * lines connecting two datapoins. ingo@804: * @param shapesVisible A boolean property to determine the visibility of ingo@804: * datapoints. ingo@804: * @param locale The Locale to specify the language. ingo@804: * @param context The CallContext object. ingo@804: */ tim@335: protected void createPDF( tim@335: OutputStream outputStream, tim@335: Collection parameters, tim@335: Collection measurements, tim@335: Collection dates, tim@335: ChartLabels chartLables, tim@335: String uuid, tim@335: String exportFormat, ingo@358: CallContext context tim@335: ) { tim@335: Chart chart = getChart( tim@335: chartLables, ingo@358: createStyle(context), tim@335: parameters, tim@335: measurements, tim@335: dates, sascha@452: getChartResult(uuid, context), ingo@1050: (Locale) context.getContextValue("locale"), tim@335: uuid, ingo@1050: Boolean.TRUE.equals(context.getContextValue("lines.visible")), ingo@1050: Boolean.TRUE.equals(context.getContextValue("shapes.visible")), sascha@439: context tim@335: ); tim@335: tim@335: if (chart == null) { tim@335: log.error("Could not initialize chart."); tim@335: return; tim@335: } tim@335: tim@335: ChartExportHelper.exportPDF( tim@335: outputStream, tim@335: chart.generateChart(), tim@335: "A4", ingo@1050: 50F, 50F, 50F, 50F, ingo@1050: context tim@335: ); tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Creates a chart and writes it as svg to outputStream. ingo@804: * ingo@804: * @param outputStream The stream used to write the chart to. ingo@804: * @param parameters A collection with parameters. ingo@804: * @param measurements A collection with measurements. ingo@804: * @param dates A collection with dates. ingo@804: * @param chartLables The labels used to decorate the chart. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @param locale The Locale to specify the language. ingo@804: * @param width The width of the chart. ingo@804: * @param height The height of the chart. ingo@804: * @param linesVisible A boolean property to determine the visibility of ingo@804: * lines connecting two datapoins. ingo@804: * @param shapesVisible A boolean property to determine the visibility of ingo@804: * datapoints. ingo@804: * @param callContext The CallContext object. ingo@804: */ tim@335: protected void createSVG( tim@335: OutputStream outputStream, tim@335: Collection parameters, tim@335: Collection measurements, tim@335: Collection dates, tim@335: ChartLabels chartLables, tim@335: String uuid, tim@335: Locale locale, tim@335: int width, tim@335: int height, tim@335: boolean linesVisible, ingo@358: boolean shapesVisible, ingo@358: CallContext callContext tim@335: ) { tim@335: Chart chart = getChart( tim@335: chartLables, ingo@358: createStyle(callContext), tim@335: parameters, tim@335: measurements, tim@335: dates, sascha@446: getChartResult(uuid, callContext), tim@335: locale, tim@335: uuid, tim@335: linesVisible, sascha@439: shapesVisible, sascha@439: callContext tim@335: ); tim@335: tim@335: if (chart == null) { tim@335: log.error("Could not initialize chart."); tim@335: return; tim@335: } tim@335: tim@335: ChartExportHelper.exportSVG( tim@335: outputStream, tim@335: chart.generateChart(), tim@335: null, ingo@1048: width, height tim@335: ); tim@335: tim@335: log.debug("svg export finished."); tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * This method creates a chart and returns it. In normal case, this is the ingo@804: * only method to be overriden by subclasses to create other types of ingo@804: * charts. ingo@804: * ingo@804: * @param chartLables Labels used to decorate the chart. ingo@804: * @param theme The theme used to adjust the look of the chart. ingo@804: * @param parameters A collection with parameters this chart contains. ingo@804: * @param measurements A collection with measurement this chart contains. ingo@804: * @param dates A collection with dates this chart contains. ingo@804: * @param result The data collection used to be displayed in this chart. ingo@804: * @param locale The Locale used to determine the language. ingo@804: * @param uuid The uuid of the current artifact. ingo@804: * @param linesVisible A boolean property to determine the visibility of ingo@804: * lines connecting two points in a chart (not used in this chart type). ingo@804: * @param shapesVisible A boolean property to determine the visiblity of ingo@804: * datapoints in this chart (not used in this chart type). ingo@804: * @param callContext The CallContext object. ingo@804: * @return a timeseries chart. ingo@804: */ tim@335: protected Chart getChart( tim@335: ChartLabels chartLables, ingo@358: ChartTheme theme, tim@335: Collection parameters, tim@335: Collection measurements, tim@335: Collection dates, ingo@429: Object result, tim@335: Locale locale, tim@335: String uuid, tim@335: boolean linesVisible, sascha@439: boolean shapesVisible, sascha@439: CallContext callContext tim@335: ) { tim@335: Chart chart = null; tim@335: tim@335: if (CACHE_CHART) { tim@335: log.info("Try to get timeseries chart from cache."); sascha@439: chart = (Chart) getChartFromCache(uuid, callContext); tim@335: } tim@335: tim@335: if (chart != null) tim@335: return chart; tim@335: tim@335: log.info("Chart not in cache yet."); tim@335: chart = new TimeSeriesChart( tim@335: chartLables, ingo@358: theme, tim@335: parameters, tim@335: measurements, tim@335: dates, ingo@429: (Collection)result, tim@335: timeGapDefinitions, tim@335: locale, tim@335: linesVisible, tim@335: shapesVisible tim@335: ); tim@335: chart.generateChart(); tim@335: tim@335: if (CACHE_CHART) { tim@335: log.info("Put chart into cache."); tim@335: purifyChart(chart, uuid); tim@335: } tim@335: tim@335: return chart; tim@335: } tim@335: ingo@804: ingo@804: /** ingo@804: * Fetches the ChartTheme from callContext and returns it. ingo@804: * ingo@804: * @param callContext CallContext objects storing a chart theme. ingo@804: * @return a chart theme. ingo@804: */ ingo@358: protected ChartTheme createStyle(CallContext callContext) { ingo@358: log.debug("Fetch chart theme from global context"); tim@335: ingo@358: GNVArtifactContext context = ingo@358: (GNVArtifactContext) callContext.globalContext(); ingo@358: ingo@358: XMLChartTheme theme = (XMLChartTheme) context.get( sascha@443: GNVArtifactContext.CHART_TEMPLATE_KEY); tim@335: tim@335: return theme; tim@335: } tim@335: ingo@804: /** ingo@804: * Creates a ChartLabels object storing different labels used to decorate a ingo@804: * chart. ingo@804: * ingo@804: * @param locale The Locale object to adjust the language of labels. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return the chart labels. ingo@804: */ ingo@492: protected ChartLabels createChartLabels(Locale locale, String uuid) { ingo@492: return new ChartLabels( ingo@492: createChartTitle(locale, uuid), ingo@492: createChartSubtitle(locale, uuid), ingo@492: RessourceFactory.getInstance().getRessource( ingo@492: locale, ingo@492: domainLable, ingo@492: domainLable ingo@492: ) ingo@492: ); ingo@492: } ingo@492: tim@335: ingo@804: /** ingo@804: * Creates and returns the chart title. ingo@804: * ingo@804: * @param locale The Locale used to adjust the language of the title. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return the name of the selected fis. ingo@804: */ tim@335: protected String createChartTitle(Locale locale, String uuid) { tim@335: return getFisName(locale); tim@335: tim@335: } tim@335: tim@335: ingo@804: /** ingo@839: * Creates and returns the subtitle of a chart. The subtitle in this class ingo@839: * contains the station and the time interval. ingo@804: * ingo@804: * @param locale The Locale used to adjust the language of the subtitle. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return the selected feature. ingo@804: */ tim@335: protected String createChartSubtitle(Locale locale, String uuid) { ingo@839: String subtitle = ""; ingo@839: subtitle += getSelectedFeatureName(uuid); ingo@839: ingo@839: InputData data = inputData.get(timeIntervalValueName); ingo@839: if (data != null){ ingo@839: Object describeData = data.getObject(); ingo@839: if (describeData instanceof MinMaxDescribeData) { ingo@839: MinMaxDescribeData tmp = (MinMaxDescribeData) describeData; ingo@839: ingo@839: DateFormat format = DateFormat.getDateTimeInstance( ingo@839: DateFormat.MEDIUM, DateFormat.SHORT, locale); ingo@839: ingo@839: String lDateStr = (String) tmp.getMinValue(); ingo@839: String rDateStr = (String) tmp.getMaxValue(); ingo@839: ingo@839: try { ingo@839: Date lDate = srcFormat.parse(lDateStr); ingo@839: Date rDate = srcFormat.parse(rDateStr); ingo@839: ingo@839: String interval = format.format(lDate); ingo@839: interval += " - "; ingo@839: interval += format.format(rDate); ingo@839: ingo@839: if (subtitle != null && subtitle.length() > 0) ingo@839: subtitle += ", "; ingo@839: ingo@839: subtitle += interval; ingo@839: } ingo@839: catch (ParseException pe) { ingo@839: log.error(pe, pe); ingo@839: } ingo@839: } ingo@839: } ingo@839: ingo@839: return subtitle; tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Returns the selected fis name. ingo@804: * ingo@804: * @param locale The Locale object used to adjust the language. ingo@804: * @return the name of the fis. ingo@804: */ tim@335: protected String getFisName(Locale locale) { tim@335: String returnValue = ""; tim@335: InputData input = inputData.get("fisname"); tim@335: tim@335: if (input != null) { tim@335: String value = input.getValue(); tim@335: tim@335: returnValue = RessourceFactory.getInstance().getRessource( tim@335: locale, tim@335: value, tim@335: value tim@335: ); tim@335: } tim@335: return returnValue; tim@335: } tim@335: tim@335: ingo@804: /** ingo@804: * Returns the name of the selected object in the collection specified by ingo@804: * key. ingo@804: * ingo@804: * @param uuid The UUID of the current artifat. ingo@804: * @param key A key to specify a collection. ingo@804: * @return the name of the selected item. ingo@804: */ ingo@492: protected String getSelectedInputDataName(String uuid, String key) { ingo@492: Collection values = getCollection(key, uuid); ingo@492: ingo@492: if (values != null) { ingo@492: Iterator it = values.iterator(); ingo@492: ingo@492: while (it.hasNext()) { ingo@492: KeyValueDescibeData data = (KeyValueDescibeData) it.next(); ingo@492: ingo@492: if (data.isSelected()) { ingo@492: return data.getValue(); ingo@492: } ingo@492: } ingo@492: } ingo@492: return null; ingo@492: } ingo@492: ingo@492: ingo@804: /** ingo@804: * Returns a collection of selected parameters. ingo@804: * ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return selected parameters. ingo@804: */ tim@335: protected Collection getParameters(String uuid) { tim@335: return this.getCollection(parameterValuesName, uuid); tim@335: } tim@335: ingo@804: /** ingo@804: * Returns a collection of selected measurements. ingo@804: * ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return selected measurements. ingo@804: */ tim@335: protected Collection getMeasurements(String uuid) { tim@335: return this.getCollection(measuremenValueName, uuid); tim@335: } ingo@804: ingo@804: /** ingo@804: * Returns a collection of selected dates. ingo@804: * ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return selected dates. ingo@804: */ tim@335: protected Collection getDates(String uuid) { tim@335: return this.getCollection(dateValueName,uuid); tim@335: } tim@335: tim@335: @Override tim@335: public void setup(Node configuration) { tim@335: super.setup(configuration); tim@335: String featureNameValue = Config.getStringXPath(configuration, tim@335: "value-names/value-name[@name='feature']/@value"); tim@335: if (featureNameValue != null) { tim@335: this.featureValuesName = featureNameValue; tim@335: } tim@335: String parameterNameValue = Config.getStringXPath(configuration, tim@335: "value-names/value-name[@name='parameter']/@value"); tim@335: if (parameterNameValue != null) { tim@335: this.parameterValuesName = parameterNameValue; tim@335: } tim@335: String measurementNameValue = Config.getStringXPath(configuration, tim@335: "value-names/value-name[@name='measurement']/@value"); tim@335: if (measurementNameValue != null) { tim@335: this.measuremenValueName = measurementNameValue; tim@335: } tim@812: String timeIntervalValue = Config.getStringXPath(configuration, tim@812: "value-names/value-name[@name='timeinterval']/@value"); tim@812: if (timeIntervalValue != null){ tim@812: this.timeIntervalValueName = timeIntervalValue; tim@812: } sascha@778: tim@335: String dateNameValue = Config.getStringXPath(configuration, tim@335: "value-names/value-name[@name='date']/@value"); tim@335: if (dateNameValue != null) { tim@335: this.dateValueName = dateNameValue; tim@335: } tim@335: if (timeGapDefinitions == null){ tim@335: Element gapDefinition = (Element)Config.getNodeXPath(configuration, tim@335: "time-gap-definition"); tim@335: synchronized (this.getClass()) { tim@335: if (gapDefinition != null){ tim@335: String link = gapDefinition.getAttribute("xlink:href"); tim@335: if (link != null ){ tim@335: String absolutFileName = Config.replaceConfigDir(link); tim@335: gapDefinition = (Element)new ArtifactXMLUtilities(). tim@335: readConfiguration(absolutFileName); tim@335: } sascha@778: sascha@778: NodeList gapDefinitions = Config.getNodeSetXPath(gapDefinition, tim@335: "/time-gaps/time-gap"); tim@335: if (gapDefinition != null){ tim@335: timeGapDefinitions = new ArrayList(gapDefinitions. tim@335: getLength()); tim@335: for (int i = 0; i < gapDefinitions.getLength(); i++){ tim@335: Element gapNode = (Element)gapDefinitions.item(i); tim@335: String unit = gapNode.getAttribute("unit"); tim@335: int key = Integer.parseInt(gapNode.getAttribute("key")); tim@335: int value = Integer.parseInt(gapNode.getAttribute("gap")); tim@335: log.info("Add new Timegap: "+key+" "+value+" "+ unit); sascha@778: timeGapDefinitions.add(new DefaultTimeGap(unit, sascha@778: key, tim@335: value)); tim@335: } tim@335: } tim@335: } tim@335: } tim@335: } tim@335: } ingo@610: ingo@804: tim@335: /** ingo@804: * Creates a NamedArrayList storing some ingo@804: * KeyValueDescibeData objects found in the input data with the key ingo@804: * collectionName. ingo@804: * ingo@804: * @param collectionName String to specify the input data. ingo@804: * @param uuid The UUID of the current artifact. ingo@804: * @return a collection with values from input data. tim@335: */ tim@335: protected Collection getCollection( ingo@610: String collectionName, ingo@610: String uuid) ingo@610: { ingo@838: log.debug("Search for input data: " + collectionName); ingo@610: NamedCollection c = new NamedArrayList(collectionName); tim@335: ingo@838: InputData data = null; ingo@838: State parent = this; ingo@838: do { ingo@838: data = parent.inputData().get(collectionName); ingo@838: ingo@838: if (data != null) { ingo@838: break; ingo@838: } ingo@838: } ingo@838: while ((parent = parent.getParent()) != null); ingo@838: ingo@610: if (data == null) { ingo@610: log.warn("No collection found with name: " + collectionName); ingo@610: return c; tim@335: } ingo@610: ingo@615: String[] descs = data.getDescription(); ingo@615: String[] values = data.splitValue(); ingo@615: int size = values.length; ingo@615: ingo@615: for (int i = 0; i < size; i++){ ingo@615: c.add(new DefaultKeyValueDescribeData( ingo@615: values[i], descs[i], getID())); ingo@615: } ingo@610: ingo@610: return c; tim@335: } tim@335: } tim@335: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :