tim@64: /** tim@64: * tim@64: */ tim@64: package de.intevation.gnv.transition.timeseries; tim@64: tim@68: import java.io.IOException; ingo@304: import java.io.File; tim@73: import java.io.OutputStream; tim@177: import java.io.UnsupportedEncodingException; tim@217: import java.util.ArrayList; tim@68: import java.util.Collection; tim@68: import java.util.Iterator; tim@217: import java.util.List; ingo@300: import java.util.Locale; tim@68: ingo@299: import javax.imageio.ImageIO; tim@95: import javax.xml.transform.Transformer; tim@95: import javax.xml.transform.TransformerConfigurationException; tim@95: import javax.xml.transform.TransformerException; tim@95: import javax.xml.transform.TransformerFactory; tim@95: import javax.xml.transform.TransformerFactoryConfigurationError; tim@95: import javax.xml.transform.dom.DOMSource; tim@95: import javax.xml.transform.stream.StreamResult; tim@95: tim@68: import org.apache.log4j.Logger; tim@95: import org.w3c.dom.Document; tim@95: import org.w3c.dom.Element; tim@95: import org.w3c.dom.Node; tim@217: import org.w3c.dom.NodeList; tim@68: ingo@300: import org.jfree.chart.ChartTheme; ingo@300: tim@90: import au.com.bytecode.opencsv.CSVWriter; tim@119: import de.intevation.artifactdatabase.Config; tim@95: import de.intevation.artifactdatabase.XMLUtils; tim@117: import de.intevation.artifacts.CallMeta; tim@178: import de.intevation.artifacts.PreferredLocale; tim@178: import de.intevation.gnv.artifacts.ressource.RessourceFactory; ingo@300: import de.intevation.gnv.chart.Chart; tim@68: import de.intevation.gnv.chart.ChartLabels; ingo@299: import de.intevation.gnv.chart.TimeSeriesChart; ingo@304: import de.intevation.gnv.chart.XMLChartTheme; tim@68: import de.intevation.gnv.chart.exception.TechnicalChartException; tim@232: import de.intevation.gnv.exports.DefaultExport; ingo@301: import de.intevation.gnv.exports.DefaultDataCollector; ingo@301: import de.intevation.gnv.exports.SimpleOdvDataCollector; tim@232: import de.intevation.gnv.exports.DefaultProfile; tim@232: import de.intevation.gnv.exports.Export.Profile; tim@68: import de.intevation.gnv.geobackend.base.Result; tim@95: import de.intevation.gnv.statistics.Statistic; tim@253: import de.intevation.gnv.statistics.StatisticSet; tim@98: import de.intevation.gnv.statistics.Statistics; tim@95: import de.intevation.gnv.statistics.TimeseriesStatistics; tim@95: import de.intevation.gnv.statistics.exception.StatisticsException; tim@217: import de.intevation.gnv.timeseries.gap.DefaultTimeGap; tim@217: import de.intevation.gnv.timeseries.gap.TimeGap; tim@91: import de.intevation.gnv.transition.InputData; tim@64: import de.intevation.gnv.transition.OutputTransitionBase; tim@68: import de.intevation.gnv.transition.describedata.KeyValueDescibeData; tim@81: import de.intevation.gnv.transition.describedata.NamedCollection; tim@68: import de.intevation.gnv.transition.exception.TransitionException; tim@95: import de.intevation.gnv.utils.ArtifactXMLUtilities; tim@64: ingo@230: tim@64: /** tim@64: * @author Tim Englich tim@117: * tim@64: */ tim@117: public class TimeSeriesOutputTransition extends OutputTransitionBase { tim@86: ingo@300: protected static final boolean CACHE_CHART = ingo@300: Boolean.parseBoolean(System.getProperty("cache.chart", "false")); ingo@300: ingo@303: protected static final String[] IMG_EXPORT_FORMAT = { ingo@303: "PNG", "JPEG", "GIF" ingo@303: }; ingo@303: tim@86: /** tim@86: * The UID of this Class tim@86: */ tim@86: private static final long serialVersionUID = 4178407570503098858L; tim@117: tim@68: /** tim@68: * the logger, used to log exceptions and additonaly information tim@68: */ tim@117: private static Logger log = Logger tim@117: .getLogger(TimeSeriesOutputTransition.class); ingo@300: tim@217: private static List timeGapDefinitions = null; tim@117: tim@117: protected String domainLable = "Zeit [UTC]"; tim@171: tim@119: protected String featureValuesName = "featureid"; tim@119: protected String parameterValuesName = "parameterid"; tim@119: protected String measuremenValueName = "measurementid"; tim@179: protected String dateValueName = "dateid"; ingo@230: ingo@230: public static final String [] TIMESERIES_CSV_PROFILE_NAMES = { ingo@230: "XORDINATE", ingo@230: "YORDINATE", ingo@230: "GROUP1", ingo@230: "GROUP2", ingo@230: "GROUP3" ingo@230: }; tim@232: tim@232: public static final String [] TIMESERIES_ODV_PROFILE_NAMES = { tim@232: "CRUISE", tim@232: "STATION", tim@232: "TYPE", ingo@238: "SHAPE", tim@232: "BOTDEPTH", tim@247: "DEPTH", tim@247: "TIMEVALUE", tim@247: "DATAVALUE", tim@247: "PARAMETER" ingo@239: }; ingo@239: ingo@239: ingo@239: public static final String [] ODV_COLUMN_HEADER = { ingo@239: "Cruise", ingo@239: "Station", ingo@239: "Type", ingo@239: "Longitude [deegrees_east]", ingo@239: "Latitude [deegrees_north]", tim@241: "Bot. Depth [m]", tim@247: "Depth [m]", tim@247: "Date/Time", tim@247: "Value", tim@247: "Parameterid" ingo@239: }; ingo@230: ingo@230: /** ingo@230: * Profile for exporting data to cvs ingo@230: */ ingo@230: public static final Profile TIMESERIES_CSV_PROFILE = ingo@230: new DefaultProfile( ingo@239: null, ingo@230: ',', ingo@230: '"', ingo@230: '"', ingo@230: "CSV", ingo@230: "ISO-8859-1"); ingo@230: ingo@230: /** ingo@230: * Profile for exporting data to odv ingo@230: * TODO Change TIMESERIES_PROFILE_NAMES, which belong to CSV exports ingo@230: */ ingo@230: public static final Profile TIMESERIES_ODV_PROFILE = ingo@230: new DefaultProfile( ingo@239: ODV_COLUMN_HEADER, ingo@230: '\t', ingo@230: CSVWriter.NO_QUOTE_CHARACTER, ingo@230: CSVWriter.NO_ESCAPE_CHARACTER, ingo@230: "ODV", ingo@230: "ISO-8859-1"); tim@117: tim@64: /** tim@64: * Constructor tim@64: */ tim@64: public TimeSeriesOutputTransition() { tim@64: super(); tim@64: } tim@64: tim@64: /** tim@117: * @see de.intevation.gnv.transition.OutputTransition#out(java.lang.String, tim@117: * java.util.Collection, java.io.OutputStream, java.lang.String, tim@117: * de.intevation.artifacts.CallMeta) tim@117: */ ingo@303: public void out( ingo@303: Document format, ingo@303: Collection inputData, ingo@303: OutputStream outputStream, ingo@303: String uuid, ingo@303: CallMeta callMeta ingo@303: ) throws TransitionException ingo@303: { tim@90: log.debug("TimeSeriesOutputTransition.out"); ingo@303: ingo@303: String outputMode = Config.getStringXPath( ingo@303: format, ingo@303: "action/out/@name" ingo@303: ); ingo@303: String mimeType = Config.getStringXPath( ingo@303: format, ingo@303: "action/out/mime-type/@value" ingo@303: ); ingo@303: tim@68: try { tim@117: tim@232: this.advance(uuid, callMeta); // TODO This hsould only be done if it is nessessary ingo@303: tim@117: if (outputMode.equalsIgnoreCase("chart")) { tim@90: log.debug("Chart will be generated."); tim@91: int chartWidth = 600; tim@91: int chartHeight = 400; tim@91: try { tim@117: if (inputData != null) { tim@91: Iterator it = inputData.iterator(); tim@117: while (it.hasNext()) { tim@91: InputData ip = it.next(); tim@117: if (ip.getName().equalsIgnoreCase("width")) { tim@91: chartWidth = Integer.parseInt(ip.getValue()); tim@117: } else if (ip.getName().equalsIgnoreCase("height")) { tim@91: chartHeight = Integer.parseInt(ip.getValue()); tim@91: } tim@91: } tim@91: } tim@91: } catch (NumberFormatException e) { tim@117: log.error(e, e); tim@91: throw new TransitionException(e); tim@91: } tim@207: Collection parameters = this.getParameters(uuid); tim@207: Collection measurements = this.getMeasurements(uuid); tim@207: Collection dates = this.getDates(uuid); tim@178: ChartLabels chartLables = new ChartLabels(this.getFisName(callMeta.getLanguages())+" "+this tim@207: .getSelectedFeatureName(uuid), this.domainLable); ingo@303: ingo@315: String exportFormat = getExportFormat(mimeType); ingo@315: ingo@315: PreferredLocale[] locales = callMeta.getLanguages(); ingo@315: Locale[] serverLocales = ingo@315: RessourceFactory.getInstance().getLocales(); ingo@315: Locale locale = ingo@315: callMeta.getPreferredLocale(serverLocales); ingo@315: ingo@315: log.debug( ingo@315: "Best locale - regarding intersection of server and " + ingo@315: "browser locales - is " + locale.toString() ingo@315: ); ingo@303: ingo@303: this.createChart( ingo@303: outputStream, ingo@303: parameters, ingo@303: measurements, ingo@303: dates, ingo@303: chartLables, ingo@303: uuid, ingo@304: exportFormat, ingo@315: locale, ingo@304: chartWidth, ingo@304: chartHeight ingo@303: ); tim@117: } else if (outputMode.equalsIgnoreCase("csv")) { tim@90: log.debug("CSV-File will be generated."); tim@232: Collection chartResult = this.getChartResult(uuid); tim@177: this.createCSV(outputStream, chartResult); tim@117: } else if (outputMode.equalsIgnoreCase("statistics")) { tim@95: log.debug("Statistics will be generated."); tim@98: Statistics s = getStatisticsGenerator(); tim@232: Collection chartResult = this.getChartResult(uuid); tim@253: Collection parameters = tim@253: this.getParameters(uuid); tim@253: Collection measurements = tim@253: this.getMeasurements(uuid); tim@253: Collection dates = tim@253: this.getDates(uuid); tim@253: Collection statistics = tim@253: s.calculateStatistics(chartResult, tim@253: parameters, tim@253: measurements, tim@253: dates); tim@95: Document doc = this.writeStatistics2XML(statistics); tim@95: this.writeDocument2OutputStream(doc, outputStream); ingo@230: } else if (outputMode.equalsIgnoreCase("odv")) { tim@232: tim@232: Collection odvResult = this.getODVResult(uuid); tim@232: this.createODV(outputStream, odvResult); tim@90: } tim@68: } catch (IOException e) { tim@117: log.error(e, e); tim@68: throw new TransitionException(e); tim@68: } catch (TechnicalChartException e) { tim@117: log.error(e, e); tim@68: throw new TransitionException(e); tim@117: } catch (StatisticsException e) { tim@117: log.error(e, e); tim@95: throw new TransitionException(e); tim@68: } tim@68: } tim@98: ingo@303: ingo@303: protected String getExportFormat(String mime) { ingo@303: for(int i = 0; i < IMG_EXPORT_FORMAT.length; i++) { ingo@303: if (mime.trim().toUpperCase().indexOf(IMG_EXPORT_FORMAT[i]) > 0) ingo@303: return IMG_EXPORT_FORMAT[i]; ingo@303: } ingo@303: ingo@303: // no format found relating to mimeType, default export as PNG ingo@303: return IMG_EXPORT_FORMAT[0]; ingo@303: } ingo@303: ingo@303: tim@98: /** tim@177: * @param outputStream tim@177: * @param chartResult tim@177: * @throws UnsupportedEncodingException tim@177: * @throws IOException tim@177: * @throws TransitionException tim@177: */ tim@177: protected void createCSV(OutputStream outputStream, tim@177: Collection chartResult) tim@177: throws UnsupportedEncodingException, tim@177: IOException, tim@177: TransitionException { ingo@233: DefaultExport export = new DefaultExport(new DefaultDataCollector( ingo@233: TIMESERIES_CSV_PROFILE_NAMES)); ingo@230: ingo@230: export.create(TIMESERIES_CSV_PROFILE, outputStream, chartResult); ingo@230: } ingo@230: ingo@230: /** ingo@230: * TODO Result is not used at the moment. Change result with correct data. ingo@230: */ ingo@230: protected void createODV(OutputStream outputStream, Collection result) ingo@230: throws IOException, TransitionException { ingo@230: ingo@238: DefaultExport export = new DefaultExport(new SimpleOdvDataCollector( ingo@238: TIMESERIES_ODV_PROFILE_NAMES)); ingo@230: ingo@238: if (result == null) ingo@238: log.error("#################### RESULT == NULL #################"); ingo@230: export.create(TIMESERIES_ODV_PROFILE, outputStream, result); tim@177: } tim@177: tim@177: /** tim@98: * @return tim@98: */ tim@98: protected Statistics getStatisticsGenerator() { tim@98: Statistics s = new TimeseriesStatistics(); tim@98: return s; tim@98: } tim@117: tim@117: protected void writeDocument2OutputStream(Document document, OutputStream os) { tim@117: tim@95: try { tim@117: TransformerFactory transformerFactory = TransformerFactory tim@117: .newInstance(); tim@95: Transformer transformer = transformerFactory.newTransformer(); tim@95: DOMSource source = new DOMSource(document); tim@117: StreamResult result = new StreamResult(os); tim@95: transformer.transform(source, result); tim@95: } catch (TransformerConfigurationException e) { tim@117: log.error(e, e); tim@95: } catch (TransformerFactoryConfigurationError e) { tim@117: log.error(e, e); tim@117: } catch (TransformerException e) { tim@117: log.error(e, e); tim@95: } tim@95: } tim@117: tim@253: protected Document writeStatistics2XML( Collection statistic) { tim@95: ArtifactXMLUtilities xmlUtilities = new ArtifactXMLUtilities(); tim@95: Document doc = XMLUtils.newDocument(); tim@117: if (statistic != null) { tim@117: Node statisticResults = xmlUtilities.createArtifactElement(doc, tim@253: "statistics"); tim@95: doc.appendChild(statisticResults); tim@253: Iterator it = statistic.iterator(); tim@117: while (it.hasNext()) { tim@253: StatisticSet set = it.next(); tim@253: Element setElement = xmlUtilities.createArtifactElement(doc, tim@253: "statistic"); tim@253: setElement.setAttribute("name", set.getName()); tim@253: tim@253: Iterator sit = set.getStatistics().iterator(); tim@253: while (sit.hasNext()){ tim@253: Statistic s = sit.next(); tim@253: Element result = xmlUtilities.createArtifactElement(doc, tim@253: "statistic-value"); tim@253: result.setAttribute("name", s.getKey()); tim@253: result.setAttribute("value", s.getStringValue()); tim@253: setElement.appendChild(result); tim@253: } tim@253: statisticResults.appendChild(setElement); tim@95: } tim@117: tim@95: } tim@95: return doc; tim@95: } tim@68: tim@207: protected String getSelectedFeatureName(String uuid) { tim@117: Collection values = this tim@207: .getCollection(featureValuesName, uuid); tim@117: if (values != null) { tim@86: Iterator it = values.iterator(); tim@117: while (it.hasNext()) { tim@86: KeyValueDescibeData data = it.next(); tim@117: if (data.isSelected()) { tim@86: return data.getValue(); tim@86: } tim@86: } tim@86: } tim@86: return null; tim@86: } tim@117: tim@86: /** tim@86: * @param outputStream tim@86: * @param parameters tim@86: * @param measurements tim@86: * @param timeSeriesName tim@86: * @param chartStyle tim@86: * @param chartLables tim@86: * @throws IOException tim@86: * @throws TechnicalChartException tim@86: */ ingo@300: protected void createChart( ingo@300: OutputStream outputStream, ingo@300: Collection parameters, ingo@300: Collection measurements, ingo@300: Collection dates, ingo@300: ChartLabels chartLables, ingo@303: String uuid, ingo@304: String exportFormat, ingo@315: Locale locale, ingo@304: int width, ingo@304: int height ingo@300: ) ingo@300: throws IOException, TechnicalChartException ingo@300: { ingo@300: log.debug("Create chart."); ingo@300: Chart chart = getChart( ingo@300: chartLables, ingo@300: parameters, ingo@300: measurements, ingo@310: dates, ingo@300: getChartResult(uuid), ingo@315: locale, // Locale ingo@300: uuid ingo@300: ); ingo@300: ingo@300: if (chart == null) { ingo@300: log.error("Could not initialize chart."); ingo@300: return; ingo@300: } ingo@300: ingo@303: log.debug( ingo@303: "export chart as " + exportFormat + ingo@303: " in " + width + "x" + height ingo@303: ); ingo@303: ingo@303: ImageIO.write( ingo@303: chart.exportImage(width, height), ingo@303: exportFormat, ingo@303: outputStream ingo@303: ); ingo@300: } ingo@300: ingo@310: ingo@300: protected Chart getChart( ingo@300: ChartLabels chartLables, ingo@300: Collection parameters, ingo@300: Collection measurements, ingo@310: Collection dates, ingo@300: Collection result, ingo@300: Locale locale, ingo@300: String uuid ingo@300: ) { ingo@300: Chart chart = null; ingo@300: ingo@300: if (CACHE_CHART) { ingo@300: log.info("Try to get timeseries chart from cache."); ingo@300: chart = (Chart) getChartFromCache(uuid); ingo@300: } ingo@300: ingo@300: if (chart != null) ingo@300: return chart; ingo@300: ingo@300: log.info("Chart not in cache yet."); ingo@300: chart = new TimeSeriesChart( ingo@300: chartLables, ingo@304: createStyle(), ingo@300: parameters, ingo@300: measurements, ingo@310: dates, ingo@300: result, ingo@310: timeGapDefinitions, ingo@315: locale ingo@300: ); ingo@300: chart.generateChart(); ingo@300: ingo@300: if (CACHE_CHART) { ingo@300: log.info("Put chart into cache."); ingo@300: purifyChart(chart, uuid); ingo@300: } ingo@300: ingo@300: return chart; tim@68: } tim@117: ingo@304: protected ChartTheme createStyle() { ingo@304: XMLChartTheme theme = null; ingo@304: ingo@304: Document template = Config.getChartTemplate(); ingo@304: String name = Config.getStringXPath( ingo@304: template, ingo@304: "theme/name/@value" ingo@304: ); ingo@304: ingo@304: theme = new XMLChartTheme(name); ingo@304: theme.applyXMLConfiguration(template); ingo@304: ingo@304: return theme; tim@68: } ingo@304: tim@178: protected String getFisName(PreferredLocale[] preferredLocales){ tim@178: String returnValue = ""; tim@178: InputData inputData = this.inputData.get("fisname"); tim@178: if (inputData != null){ tim@178: returnValue = RessourceFactory.getInstance() tim@178: .getRessource(preferredLocales, tim@178: inputData.getValue(), tim@178: inputData.getValue()); tim@178: } tim@178: return returnValue; tim@178: } tim@117: tim@207: protected Collection getParameters(String uuid) { tim@207: return this.getCollection(parameterValuesName, uuid); tim@64: } tim@117: tim@207: protected Collection getMeasurements(String uuid) { tim@207: return this.getCollection(measuremenValueName, uuid); tim@119: } tim@207: protected Collection getDates(String uuid) { tim@207: return this.getCollection(dateValueName,uuid); tim@179: } tim@119: tim@119: @Override tim@119: public void setup(Node configuration) { tim@119: super.setup(configuration); tim@171: String featureNameValue = Config.getStringXPath(configuration, tim@171: "value-names/value-name[@name='feature']/@value"); tim@171: if (featureNameValue != null) { tim@119: this.featureValuesName = featureNameValue; tim@119: } tim@171: String parameterNameValue = Config.getStringXPath(configuration, tim@171: "value-names/value-name[@name='parameter']/@value"); tim@171: if (parameterNameValue != null) { tim@119: this.parameterValuesName = parameterNameValue; tim@119: } tim@171: String measurementNameValue = Config.getStringXPath(configuration, tim@171: "value-names/value-name[@name='measurement']/@value"); tim@171: if (measurementNameValue != null) { tim@119: this.measuremenValueName = measurementNameValue; tim@119: } tim@179: tim@179: String dateNameValue = Config.getStringXPath(configuration, tim@179: "value-names/value-name[@name='date']/@value"); tim@179: if (dateNameValue != null) { tim@179: this.dateValueName = dateNameValue; tim@179: } tim@217: if (timeGapDefinitions == null){ tim@217: Element gapDefinition = (Element)Config.getNodeXPath(configuration, tim@217: "time-gap-definition"); tim@217: synchronized (this.getClass()) { tim@217: if (gapDefinition != null){ tim@217: String link = gapDefinition.getAttribute("xlink:href"); tim@217: if (link != null ){ tim@217: String absolutFileName = Config.replaceConfigDir(link); tim@217: gapDefinition = (Element)new ArtifactXMLUtilities(). tim@217: readConfiguration(absolutFileName); tim@217: } tim@217: tim@217: NodeList gapDefinitions = Config.getNodeSetXPath(gapDefinition, tim@217: "/time-gaps/time-gap"); tim@217: if (gapDefinition != null){ tim@217: timeGapDefinitions = new ArrayList(gapDefinitions. tim@217: getLength()); tim@217: for (int i = 0; i < gapDefinitions.getLength(); i++){ tim@217: Element gapNode = (Element)gapDefinitions.item(i); tim@217: String unit = gapNode.getAttribute("unit"); tim@217: int key = Integer.parseInt(gapNode.getAttribute("key")); tim@217: int value = Integer.parseInt(gapNode.getAttribute("gap")); tim@217: log.info("Add new Timegap: "+key+" "+value+" "+ unit); tim@217: timeGapDefinitions.add(new DefaultTimeGap(unit, tim@217: key, tim@217: value)); tim@217: } tim@217: } tim@217: tim@217: } tim@217: } tim@217: } tim@82: } tim@217: tim@82: /** tim@82: * @param collectionName tim@82: * @return tim@82: */ tim@82: protected Collection getCollection( tim@207: String collectionName, tim@207: String uuid) { tim@207: Iterator it = this.getDescibeData(uuid).iterator(); tim@117: while (it.hasNext()) { tim@117: tim@117: Object o = it.next(); tim@117: tim@117: if (o instanceof NamedCollection) { tim@117: NamedCollection nc = (NamedCollection) o; tim@117: if (nc.getName().equals(collectionName)) { tim@117: return nc; tim@117: } tim@117: } tim@68: } tim@68: return null; tim@68: } tim@64: } ingo@315: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :