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