view gnv-artifacts/src/main/java/de/intevation/gnv/state/timeseries/TimeSeriesOutputState.java @ 502:fec85cd01497

Fixed issue105. Added option to enable/disable data points in charts. Parse this option while creating charts. gnv-artifacts/trunk@585 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Wed, 20 Jan 2010 15:17:35 +0000
parents 4080b57dcb52
children a162793b6053
line wrap: on
line source
/**
 *
 */
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.artifacts.PreferredLocale;

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.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.state.InputData;
import de.intevation.gnv.state.OutputStateBase;

import de.intevation.gnv.state.describedata.KeyValueDescibeData;
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.Iterator;
import java.util.List;
import java.util.Locale;
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();
    }

    /**
     * @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 mimeType = XMLUtils.xpathString(
            format, XPATH_MIME_TYPE, ArtifactNamespaceContext.INSTANCE);

        CallMeta callMeta         = callContext.getMeta();

        int chartWidth   = 600;
        int chartHeight  = 400;
        boolean sVisible = false;

        // 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();

                    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 {
            if (outputMode.equalsIgnoreCase("chart")) {
                log.debug("Chart will be generated.");

                PreferredLocale[] locales = callMeta.getLanguages();
                Locale[] serverLocales    =
                    RessourceFactory.getInstance().getLocales();
                Locale locale             =
                    callMeta.getPreferredLocale(serverLocales);

                log.debug(
                    "Best locale - regarding intersection of server and " +
                    "browser locales -  is " + locale.toString()
                );

                Collection parameters   = this.getCleanedParameters(uuid);
                Collection measurements = this.getMeasurements(uuid);
                Collection dates        = this.getDates(uuid);

                ChartLabels chartLables = createChartLabels(locale, uuid);

                String exportFormat = getExportFormat(mimeType);

                this.createChart(
                    outputStream,
                    parameters,
                    measurements,
                    dates,
                    chartLables,
                    callContext,
                    uuid,
                    exportFormat,
                    locale,
                    chartWidth,
                    chartHeight,
                    lVisible,
                    sVisible,
                    callContext
                );
            }
            else if (outputMode.equalsIgnoreCase("pdf")) {
                log.debug("Output mode == pdf");

                Locale[] serverLocales    =
                    RessourceFactory.getInstance().getLocales();
                Locale locale             =
                    callMeta.getPreferredLocale(serverLocales);

                log.debug(
                    "Best locale - regarding intersection of server and " +
                    "browser locales -  is " + locale.toString()
                );

                createPDF(
                    outputStream,
                    getCleanedParameters(uuid),
                    getMeasurements(uuid),
                    getDates(uuid),
                    new ChartLabels(
                        createChartTitle(locale, uuid),
                        createChartSubtitle(locale, uuid),
                        RessourceFactory.getInstance().getRessource(
                            locale,
                            domainLable,
                            domainLable
                        )
                    ),
                    uuid,
                    "A4",
                    true,
                    lVisible,
                    sVisible,
                    locale,
                    callContext
                );
            }
            else if (outputMode.equalsIgnoreCase("svg")) {
                log.debug("Output mode == svg");

                Locale[] serverLocales    =
                    RessourceFactory.getInstance().getLocales();
                Locale locale             =
                    callMeta.getPreferredLocale(serverLocales);

                log.debug(
                    "Best locale - regarding intersection of server and " +
                    "browser locales -  is " + locale.toString()
                );

                createSVG(
                    outputStream,
                    getCleanedParameters(uuid),
                    getMeasurements(uuid),
                    getDates(uuid),
                    new ChartLabels(
                        createChartTitle(locale, uuid),
                        createChartSubtitle(locale, uuid),
                        RessourceFactory.getInstance().getRessource(
                            locale,
                            domainLable,
                            domainLable
                        )
                    ),
                    uuid,
                    locale,
                    chartWidth,
                    chartHeight,
                    lVisible,
                    sVisible,
                    callContext
                );
            }
            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) {
                    Collection<KeyValueDescibeData> parameters = 
                        getParameters(uuid);

                    Collection<KeyValueDescibeData> measurements = 
                        getMeasurements(uuid);

                    Collection<KeyValueDescibeData> dates = 
                         getDates(uuid);

                    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();

                if (data.isSelected()) {
                    return data.getValue();
                }
            }
        }
        return null;
    }


    /**
     * @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 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) {
        Iterator<Object> it = this.getDescibeData(uuid).iterator();
        while (it.hasNext()) {

            Object o = it.next();

            if (o instanceof NamedCollection<?>) {
                NamedCollection<KeyValueDescibeData> nc = (NamedCollection<KeyValueDescibeData>) o;
                if (nc.getName().equals(collectionName)) {
                    return nc;
                }
            }
        }
        return null;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :

http://dive4elements.wald.intevation.org