Mercurial > dive4elements > gnv-client
diff gnv-artifacts/src/main/java/de/intevation/gnv/state/timeseries/TimeSeriesOutputState.java @ 657:af3f56758f59
merged gnv-artifacts/0.5
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:13:53 +0200 |
parents | 078ba6799bd2 |
children | dde7f51dbe1e |
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:13:53 2012 +0200 @@ -0,0 +1,948 @@ +package de.intevation.gnv.state.timeseries; + +import au.com.bytecode.opencsv.CSVWriter; + +import de.intevation.artifactdatabase.Config; +import de.intevation.artifactdatabase.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.Profile; + +import de.intevation.gnv.exports.SimpleOdvDataCollector; + +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.describedata.DefaultKeyValueDescribeData; +import de.intevation.gnv.state.describedata.KeyValueDescibeData; +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; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import java.util.ArrayList; +import java.util.Collection; +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; + +/** + * @author Tim Englich (tim.englich@intevation.de) + * @author Ingo Weinzierl (ingo.weinzierl@intevation.de) + */ +public class TimeSeriesOutputState extends OutputStateBase { + + protected static final boolean CACHE_CHART = + Boolean.parseBoolean(System.getProperty("cache.chart", "false")); + + protected static final boolean PDF_FORMAT_LANDSCAPE = + Boolean.parseBoolean(System.getProperty("export.pdf.landscape","true")); + + 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); + + private static List<TimeGap> timeGapDefinitions = null; + + protected String domainLable = "chart.timeseries.title.xaxis"; + + protected String featureValuesName = "featureid"; + protected String parameterValuesName = "parameterid"; + protected String measuremenValueName = "measurementid"; + protected String dateValueName = "dateid"; + + public static final String [] TIMESERIES_CSV_PROFILE_COLUMNS = { + "XORDINATE", + "YORDINATE", + "GROUP1", + "GROUP2", + "GROUP3" + }; + + + public static final String [] TIMESERIES_TIMESERIES_CSV_COLUMN_LABEL = { + "Date/Time", + "Value", + "ParameterID", + "MeasurementID", + "TimeseriesID" + }; + + public static final String [] TIMESERIES_MESH_CSV_COLUMN_LABEL = { + "Date/Time", + "Value", + "ParameterID", + "FeatureID", + "MeshID" + }; + + public static final String [] TIMESERIES_ODV_PROFILE_NAMES = { + "CRUISE", + "STATION", + "TYPE", + "SHAPE", + "BOTDEPTH", + "DEPTH", + "TIMEVALUE", + "DATAVALUE", + "PARAMETER" + }; + + + public static final String [] ODV_COLUMN_HEADER = { + "Cruise", + "Station", + "Type", + "Longitude [deegrees_east]", + "Latitude [deegrees_north]", + "Bot. Depth [m]", + "Depth [m]", + "Date/Time", + "Value", + "Parameterid" + }; + + /** + * Profile for exporting data to odv + * TODO Change TIMESERIES_PROFILE_NAMES, which belong to CSV exports + */ + 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(); + } + + + @Override + public void initialize(String uuid, CallContext context) + throws StateException + { + getChartResult(uuid, context); + } + + + /** + * @see de.intevation.gnv.transition.OutputTransition#out(java.lang.String, + * java.util.Collection, java.io.OutputStream, java.lang.String, + * de.intevation.artifacts.CallMeta) + */ + 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.error(e, e); + throw new StateException(e); + } + + 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")) { + createPDF( + outputStream, + parameters, + measurements, + dates, + chartLables, + uuid, + "A4", + true, + lVisible, + sVisible, + locale, + 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")) { + Collection results = (Collection) getChartResult(uuid, callContext); + requestParameter.put("locale", locale); + + 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 = createHistogramLabels( + uuid, callContext, data[i]); + + ChartTheme theme = createStyle(callContext); + + histograms[i] = new DefaultHistogram( + labels, data[i], theme, requestParameter); + } + + if (mode.equalsIgnoreCase("img")) { + ChartExportHelper.exportHistograms( + outputStream, + histograms, + exportFormat, + chartWidth, + chartHeight + ); + } + else if (mode.equalsIgnoreCase("pdf")) { + ChartExportHelper.exportHistogramsAsPDF( + outputStream, + histograms, + "A4", + PDF_FORMAT_LANDSCAPE, + 50F, 50F, 50F, 50F + ); + } + 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); + + XMLUtils.toStream(doc, outputStream); + + } + else if (outputMode.equalsIgnoreCase("odv")) { + Collection<Result> odvResult = this.getODVResult(uuid); + this.createODV(outputStream, odvResult); + } + } 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 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]; + } + + + 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; + } + + + protected Collection getCleanedParameters(String uuid) { + return getCleanedParameters(getParameters(uuid)); + } + + + 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); + } + + + /** + * TODO Result is not used at the moment. Change result with correct data. + */ + protected void createODV(OutputStream outputStream, Collection result) + throws IOException, StateException { + + DefaultExport export = new DefaultExport(new SimpleOdvDataCollector( + TIMESERIES_ODV_PROFILE_NAMES)); + + if (result == null) + log.error("#################### RESULT == NULL #################"); + export.create(TIMESERIES_ODV_PROFILE, outputStream, result); + } + + /** + * @return + */ + protected Statistics getStatisticsGenerator() { + Statistics s = new TimeseriesStatistics(); + return s; + } + + protected Document writeStatistics2XML( Collection<StatisticSet> statistic) { + ArtifactXMLUtilities xmlUtilities = new ArtifactXMLUtilities(); + Document doc = XMLUtils.newDocument(); + if (statistic != null) { + Node statisticResults = xmlUtilities.createArtifactElement(doc, + "statistics"); + doc.appendChild(statisticResults); + Iterator<StatisticSet> it = statistic.iterator(); + while (it.hasNext()) { + StatisticSet set = it.next(); + Element setElement = xmlUtilities.createArtifactElement(doc, + "statistic"); + setElement.setAttribute("name", set.getName()); + + Iterator<Statistic> sit = set.getStatistics().iterator(); + while (sit.hasNext()){ + Statistic s = sit.next(); + Element result = xmlUtilities.createArtifactElement(doc, + "statistic-value"); + result.setAttribute("name", s.getKey()); + result.setAttribute("value", s.getStringValue()); + setElement.appendChild(result); + } + statisticResults.appendChild(setElement); + } + + } + return doc; + } + + + 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 ""; + } + + + /** + * @param outputStream + * @param parameters + * @param measurements + * @param timeSeriesName + * @param chartStyle + * @param chartLables + * @throws IOException + * @throws TechnicalChartException + */ + 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 + ); + } + + + protected void createPDF( + OutputStream outputStream, + Collection parameters, + Collection measurements, + Collection dates, + ChartLabels chartLables, + String uuid, + String exportFormat, + boolean landscape, + boolean linesVisible, + boolean shapesVisible, + Locale locale, + CallContext context + ) { + Chart chart = getChart( + chartLables, + createStyle(context), + parameters, + measurements, + dates, + getChartResult(uuid, context), + locale, + uuid, + linesVisible, + shapesVisible, + context + ); + + if (chart == null) { + log.error("Could not initialize chart."); + return; + } + + ChartExportHelper.exportPDF( + outputStream, + chart.generateChart(), + "A4", + PDF_FORMAT_LANDSCAPE, + 50F, 50F, 50F, 50F + ); + } + + + 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, + 600, 400 + ); + + log.debug("svg export finished."); + } + + + 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; + } + + 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; + } + + protected ChartLabels createChartLabels(Locale locale, String uuid) { + return new ChartLabels( + createChartTitle(locale, uuid), + createChartSubtitle(locale, uuid), + RessourceFactory.getInstance().getRessource( + locale, + domainLable, + domainLable + ) + ); + } + + + protected String createChartTitle(Locale locale, String uuid) { + return getFisName(locale); + + } + + + protected String createChartSubtitle(Locale locale, String uuid) { + return getSelectedFeatureName(uuid); + } + + + protected ChartLabels createHistogramLabels( + String uuid, CallContext context, Object[] data) + { + return new ChartLabels((String) data[0], "", ""); + } + + + 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; + } + + + 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; + } + + + protected Collection<KeyValueDescibeData> getParameters(String uuid) { + return this.getCollection(parameterValuesName, uuid); + } + + protected Collection<KeyValueDescibeData> getMeasurements(String uuid) { + return this.getCollection(measuremenValueName, uuid); + } + 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 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)); + } + } + } + } + } + } + + /** + * @param collectionName + * @return + */ + protected Collection<KeyValueDescibeData> getCollection( + String collectionName, + String uuid) + { + NamedCollection<KeyValueDescibeData> c = new NamedArrayList<KeyValueDescibeData>(collectionName); + + InputData data = inputData.get(collectionName); + 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 :