view flys-artifacts/src/main/java/de/intevation/flys/exports/WaterlevelExporter.java @ 4282:8b4988815974

Added marker for Ws and Qs in Historical Discharge WQ charts. Therefore, the XYChartGenerator got two new methods addDomainMarker(Marker, boolean) and addValueMarker(Marker, boolean). The boolean parameters determine, if the marker should be visible or not. This is analogous to addAxisSeries(XYSeries, int, boolean).
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Mon, 29 Oct 2012 05:59:27 +0100
parents 13855a555d3b
children 7e7587c905ab
line wrap: on
line source
package de.intevation.flys.exports;

import java.io.IOException;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Document;

import org.apache.log4j.Logger;

import au.com.bytecode.opencsv.CSVWriter;

import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JRException;

import de.intevation.artifacts.CallContext;
import de.intevation.artifacts.CallMeta;
import de.intevation.artifacts.common.utils.Config;

import de.intevation.flys.model.Gauge;

import de.intevation.flys.artifacts.FLYSArtifact;
import de.intevation.flys.artifacts.WINFOArtifact;
import de.intevation.flys.artifacts.model.CalculationResult;
import de.intevation.flys.artifacts.model.WQCKms;
import de.intevation.flys.artifacts.model.WQKms;
import de.intevation.flys.artifacts.model.WKmsJRDataSource;
import de.intevation.flys.artifacts.model.fixings.FixRealizingResult;
import de.intevation.flys.artifacts.resources.Resources;

import de.intevation.flys.utils.FLYSUtils;
import de.intevation.flys.utils.FLYSUtils.WQ_MODE;
import de.intevation.flys.utils.Formatter;


/**
 * Generates different output formats (wst, csv, pdf) of data that resulted from
 * a waterlevel computation.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class WaterlevelExporter extends AbstractExporter {

    /** The logger used in this exporter.*/
    private static Logger logger = Logger.getLogger(WaterlevelExporter.class);

    public static final String FACET_WST = "wst";

    public static final String CSV_KM_HEADER =
        "export.waterlevel.csv.header.km";

    public static final String CSV_W_HEADER =
        "export.waterlevel.csv.header.w";

    public static final String CSV_Q_HEADER =
        "export.waterlevel.csv.header.q";

    public static final String CSV_Q_DESC_HEADER =
        "export.waterlevel.csv.header.q.desc";

    public static final String CSV_W_DESC_HEADER =
        "export.waterlevel.csv.header.w.desc";

    public static final String CSV_LOCATION_HEADER =
        "export.waterlevel.csv.header.location";

    public static final String CSV_GAUGE_HEADER =
        "export.waterlevel.csv.header.gauge";

    public static final String CSV_META_RESULT =
        "export.waterlevel.csv.meta.result";

    public static final String CSV_META_CREATION =
        "export.waterlevel.csv.meta.creation";

    public static final String CSV_META_CALCULATIONBASE =
        "export.waterlevel.csv.meta.calculationbase";

    public static final String CSV_META_RIVER =
        "export.waterlevel.csv.meta.river";

    public static final String CSV_META_RANGE =
        "export.waterlevel.csv.meta.range";

    public static final String CSV_META_GAUGE =
        "export.waterlevel.csv.meta.gauge";

    public static final String CSV_META_Q =
        "export.waterlevel.csv.meta.q";

    public static final String CSV_META_W =
        "export.waterlevel.csv.meta.w";

    public static final String CSV_NOT_IN_GAUGE_RANGE =
        "export.waterlevel.csv.not.in.gauge.range";


    public static final Pattern NUMBERS_PATTERN =
        Pattern.compile("\\D*(\\d++.\\d*)\\D*");

    public static final String DEFAULT_CSV_KM_HEADER       = "Fluss-Km";
    public static final String DEFAULT_CSV_W_HEADER        = "W [NN + m]";
    public static final String DEFAULT_CSV_Q_HEADER        = "Q [m\u00b3/s]";
    public static final String DEFAULT_CSV_Q_DESC_HEADER   = "Bezeichnung";
    public static final String DEFAULT_CSV_W_DESC_HEADER   = "W/Pegel [cm]";
    public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage";
    public static final String DEFAULT_CSV_GAUGE_HEADER    = "Bezugspegel";
    public static final String DEFAULT_CSV_NOT_IN_GAUGE_RANGE =
        "außerhalb des gewählten Bezugspegels";

    public static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode";
    public static final String JASPER_FILE     = "export.waterlevel.pdf.file";

    /** The storage that contains all WQKms objects for the different facets.*/
    protected List<WQKms[]> data;


    public void init(Document request, OutputStream out, CallContext context) {
        logger.debug("WaterlevelExporter.init");

        super.init(request, out, context);

        this.data = new ArrayList<WQKms[]>();
    }


    @Override
    public void generate()
    throws IOException
    {
        logger.debug("WaterlevelExporter.generate");

        if (facet != null && facet.equals(AbstractExporter.FACET_CSV)) {
            generateCSV();
        }
        else if (facet != null && facet.equals(FACET_WST)) {
            generateWST();
        }
        else if (facet != null && facet.equals(AbstractExporter.FACET_PDF)) {
            generatePDF();
        }
        else {
            throw new IOException("invalid facet for exporter");
        }
    }


    @Override
    protected void addData(Object d) {
        if (d instanceof CalculationResult) {
            d = ((CalculationResult)d).getData();
            if (d instanceof WQKms []) {
                data.add((WQKms [])d);
            }
            else if (d instanceof FixRealizingResult) {
                data.add(((FixRealizingResult) d).getWQKms());
            }
        }
    }


    /**
     * This method is used to prepare the column titles of waterlevel exports.
     * Titles in this export include the Q value. If a Q value matches a named
     * main value (as HQ100 or MNQ) this named main value should be used as
     * title. This method resets the name of the <i>wqkms</i> object if such
     * named main value fits to the chosen Q.
     *
     * @param winfo A WINFO Artifact.
     * @param wqkms A WQKms object that should be prepared.
     */
    protected String getColumnTitle(WINFOArtifact winfo, WQKms wqkms) {
        logger.debug("WaterlevelExporter.prepareNamedValue");

        String name = wqkms.getName();

        logger.debug("Name of WQKms = '" + name + "'");

        if (name.indexOf("W=") >= 0) {
            return name;
        }

        Matcher m = NUMBERS_PATTERN.matcher(name);

        if (m.matches()) {
            String raw = m.group(1);

            try {
                double v = Double.valueOf(raw);

                String nmv = FLYSUtils.getNamedMainValue(winfo, v);

                if (nmv != null && nmv.length() > 0) {
                    nmv  = FLYSUtils.stripNamedMainValue(nmv);
                    nmv += "=" + String.valueOf(v);
                    logger.debug("Set named main value '" + nmv + "'");

                    return nmv;
                }
            }
            catch (NumberFormatException nfe) {
                // do nothing here
            }
        }

        return name;
    }


    protected String getCSVRowTitle(WINFOArtifact winfo, WQKms wqkms) {
        logger.debug("WaterlevelExporter.prepareNamedValue");

        String name = wqkms.getName();

        logger.debug("Name of WQKms = '" + name + "'");

        WQ_MODE wqmode = FLYSUtils.getWQMode(winfo);

        if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.QGAUGE) {
            return localizeWQKms(winfo, wqkms);
        }

        Double v = wqkms.getRawValue();

        String nmv = FLYSUtils.getNamedMainValue(winfo, v);

        if (nmv != null && nmv.length() > 0) {
            nmv = FLYSUtils.stripNamedMainValue(nmv);
            logger.debug("Set named main value '" + nmv + "'");

            return nmv;
        }

        return localizeWQKms(winfo, wqkms);
    }


    /**
     * Get a string like 'W=' or 'Q=' with a number following in localized
     * format.
     */
    protected String localizeWQKms(WINFOArtifact winfo, WQKms wqkms) {
        WQ_MODE wqmode   = FLYSUtils.getWQMode(winfo);
        Double  rawValue = wqkms.getRawValue();

        if (rawValue == null) {
            return wqkms.getName();
        }

        NumberFormat nf = Formatter.getRawFormatter(context);

        if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.WGAUGE) {
            return "W=" + nf.format(rawValue);
        }
        else {
            return "Q=" + nf.format(rawValue);
        }
    }


    @Override
    protected void writeCSVData(CSVWriter writer) {
        logger.info("WaterlevelExporter.writeData");

        WQ_MODE mode    = FLYSUtils.getWQMode((FLYSArtifact)master);
        boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
        boolean isQ     = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
        FLYSUtils.WQ_INPUT input
            = FLYSUtils.getWQInputMode((FLYSArtifact)master);

        writeCSVMeta(writer);
        writeCSVHeader(writer, atGauge, isQ);

        for (WQKms[] tmp: data) {
            for (WQKms wqkms: tmp) {
                wQKms2CSV(writer, wqkms, atGauge, isQ);
            }
        }
    }


    protected void writeCSVMeta(CSVWriter writer) {
        logger.info("WaterlevelExporter.writeCSVMeta");

        CallMeta meta = context.getMeta();

        FLYSArtifact flys = (FLYSArtifact) master;

        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_RESULT,
                CSV_META_RESULT,
                new Object[] { FLYSUtils.getRivername(flys) })
        });

        Locale locale = Resources.getLocale(meta);
        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);

        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_CREATION,
                CSV_META_CREATION,
                new Object[] { df.format(new Date()) })
        });

        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_CALCULATIONBASE,
                CSV_META_CALCULATIONBASE,
                new Object[] { "" }) // TODO what is required at this place?
        });

        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_RIVER,
                CSV_META_RIVER,
                new Object[] { FLYSUtils.getRivername(flys) })
        });

        double[] kms = FLYSUtils.getKmRange(flys);
        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_RANGE,
                CSV_META_RANGE,
                new Object[] { kms[0], kms[kms.length-1] })
        });

        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_GAUGE,
                CSV_META_GAUGE,
                new Object[] { FLYSUtils.getGaugename(flys) })
        });

        FLYSUtils.WQ_MODE wq = FLYSUtils.getWQMode(flys);
        if (wq == FLYSUtils.WQ_MODE.QFREE || wq == FLYSUtils.WQ_MODE.QGAUGE) {
            double[] qs  = FLYSUtils.getQs(flys);
            FLYSUtils.WQ_INPUT input = FLYSUtils.getWQInputMode(flys);

            String data = "";

            if ((input == FLYSUtils.WQ_INPUT.ADAPTED ||
                input == FLYSUtils.WQ_INPUT.RANGE) &&
                qs != null && qs.length > 0)
            {
                data = String.valueOf(qs[0]);
                data += " - " + String.valueOf(qs[qs.length-1]);
            }
            else if (input == FLYSUtils.WQ_INPUT.SINGLE && qs != null){
                data = String.valueOf(qs[0]);
                for (int i = 1; i < qs.length; i++) {
                    data += ", " + String.valueOf(qs[i]);
                }
            }
            else {
                logger.warn("Could not determine Q range!");
            }

            writer.writeNext(new String[] {
                Resources.getMsg(
                    meta,
                    CSV_META_Q,
                    CSV_META_Q,
                    new Object[] {data})
            });
        }
        else {
            double[] ws = FLYSUtils.getWs(flys);

            String lower = "";
            String upper = "";

            if (ws != null && ws.length > 0) {
                lower = String.valueOf(ws[0]);
                upper = String.valueOf(ws[ws.length-1]);
            }
            else {
                logger.warn("Could not determine W range!");
            }

            writer.writeNext(new String[] {
                Resources.getMsg(
                    meta,
                    CSV_META_W,
                    CSV_META_W,
                    new Object[] { lower, upper })
            });
        }

        writer.writeNext(new String[] { "" });
    }


    protected void writeCSVHeader(
        CSVWriter writer,
        boolean   atGauge,
        boolean   isQ
    ) {
        logger.info("WaterlevelExporter.writeCSVHeader");

        if (atGauge) {
            writer.writeNext(new String[] {
                msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
                msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
                msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
                (isQ
                    ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER)
                    : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)),
                msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER),
                msg(CSV_GAUGE_HEADER, DEFAULT_CSV_GAUGE_HEADER)
            });
        }
        else {
            writer.writeNext(new String[] {
                msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
                msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER),
                msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
                msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER)
            });
        }
    }


    /**
     * Write "rows" of csv data from wqkms with writer.
     */
    protected void wQKms2CSV(
        CSVWriter writer,
        WQKms     wqkms,
        boolean   atGauge,
        boolean   isQ
    ) {
        logger.debug("WaterlevelExporter.wQKms2CSV");

        NumberFormat kmf = getKmFormatter();
        NumberFormat wf  = getWFormatter();
        NumberFormat qf  = getQFormatter();

        int      size   = wqkms.size();
        double[] result = new double[3];

        FLYSArtifact flys       = (FLYSArtifact) master;
        Gauge        gauge      = FLYSUtils.getGauge(flys);
        String       gaugeName  = gauge.getName();
        String       desc       = "";
        String       notinrange = msg(
            CSV_NOT_IN_GAUGE_RANGE,
            DEFAULT_CSV_NOT_IN_GAUGE_RANGE);

        double a = gauge.getRange().getA().doubleValue();
        double b = gauge.getRange().getB().doubleValue();

        if (flys instanceof WINFOArtifact && isQ) {
            desc = getCSVRowTitle((WINFOArtifact)flys, wqkms);
        }
        else if (!isQ) {
            Double value = FLYSUtils.getValueFromWQ(wqkms);
            desc         = value != null
                ? Formatter.getWaterlevelW(context).format(value) : null;
        }

        long startTime = System.currentTimeMillis();

        String colDesc = desc;
        if (flys instanceof WINFOArtifact) {
            if (wqkms != null && wqkms.getRawValue() != null) {
                WINFOArtifact winfo = (WINFOArtifact) flys;
                colDesc = FLYSUtils.getNamedMainValue(winfo, wqkms.getRawValue());
            }
        }

        for (int i = 0; i < size; i ++) {
            result = wqkms.get(i, result);

            if (atGauge) {
                writer.writeNext(new String[] {
                    kmf.format(result[2]),
                    wf.format(result[0]),
                    qf.format(result[1]),
                    colDesc,
                    FLYSUtils.getLocationDescription(flys, result[2]),
                    result[2] >= a && result[2] <= b
                        ? gaugeName
                        : notinrange
                });
            }
            else {
                writer.writeNext(new String[] {
                    kmf.format(result[2]),
                    wf.format(result[0]),
                    qf.format(result[1]),
                    FLYSUtils.getLocationDescription(flys, result[2])
                });
            }
        }

        long stopTime = System.currentTimeMillis();

        if (logger.isDebugEnabled()) {
            logger.debug("Writing CSV took " +
                (float)(stopTime-startTime)/1000f + " secs.");
        }
    }


    /**
     * Generates the output in WST format.
     */
    protected void generateWST()
    throws    IOException
    {
        logger.info("WaterlevelExporter.generateWST");

        int cols = data.get(0).length;
        WstWriter writer = new WstWriter(cols);

        writeWSTData(writer);

        writer.write(out);
    }


    protected void writeWSTData(WstWriter writer) {
        logger.debug("WaterlevelExporter.writeWSTData");

        double[] result = new double[4];

        for (WQKms[] tmp: data) {
            for (WQKms wqkms: tmp) {
                int size = wqkms != null ? wqkms.size() : 0;

                addWSTColumn(writer, wqkms);

                for (int i = 0; i < size; i++) {
                    result = wqkms.get(i, result);

                    writer.add(result);
                }

                if (wqkms instanceof WQCKms) {
                    addWSTColumn(writer, wqkms);

                    for (int c = 0; c < size; c++) {
                        result = wqkms.get(c, result);

                        writer.addCorrected(result);
                    }
                }
            }
        }
    }


    /**
     * This method is used to register a new column at <i>writer</i>. The name /
     * title of the column depends on the Q or W value of <i>wqkms</i>. If a Q
     * was selected and the Q fits to a named main value, the title is set to
     * the named main value. Otherwise, the name returned by
     * <i>WQKms.getName()</i> is set.
     *
     * @param writer The WstWriter.
     * @param wqkms The new WST column.
     */
    protected void addWSTColumn(WstWriter writer, WQKms wqkms) {
        if (master instanceof WINFOArtifact) {
            writer.addColumn(getColumnTitle((WINFOArtifact) master, wqkms));
        }
        else {
            writer.addColumn(wqkms.getName());
        }
    }


    /**
     *
     */
    @Override
    protected void writePDF(OutputStream out) {
        logger.debug("write PDF");
        WKmsJRDataSource source = createJRData();

        String jasperFile = Resources.getMsg(
                                context.getMeta(),
                                JASPER_FILE,
                                "/jasper/waterlevel_en.jasper");
        String confPath = Config.getConfigDirectory().toString();


        Map parameters = new HashMap();
        parameters.put("ReportTitle", "Exported Data");
        try {
            JasperPrint print = JasperFillManager.fillReport(
                confPath + jasperFile,
                parameters,
                source);
            JasperExportManager.exportReportToPdfStream(print, out);
        }
        catch(JRException je) {
            logger.warn("Error generating PDF Report!");
            je.printStackTrace();
        }
    }

    protected WKmsJRDataSource createJRData() {
        WKmsJRDataSource source = new WKmsJRDataSource();

        WQ_MODE mode    = FLYSUtils.getWQMode((FLYSArtifact)master);
        boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
        boolean isQ     = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;

        addMetaData(source);
        for (WQKms[] tmp: data) {
            for (WQKms wqkms: tmp) {
                addWKmsData(wqkms, atGauge, isQ, source);
            }
        }
        return source;
    }

    protected void addMetaData(WKmsJRDataSource source) {
        CallMeta meta = context.getMeta();

        FLYSArtifact flys = (FLYSArtifact) master;

        source.addMetaData ("river", FLYSUtils.getRivername(flys));

        Locale locale = Resources.getLocale(meta);
        DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);

        source.addMetaData("date", df.format(new Date()));

        double[] kms = FLYSUtils.getKmRange(flys);
        source.addMetaData("range", kms[0] + " - " + kms[kms.length-1]);

        source.addMetaData("gauge", FLYSUtils.getGaugename(flys));

        source.addMetaData("calculation", Resources.getMsg(
                                            locale,
                                            PDF_HEADER_MODE,
                                            "Waterlevel"));
    }

    protected void addWKmsData(
        WQKms wqkms,
        boolean atGauge,
        boolean isQ,
        WKmsJRDataSource source)
    {
        logger.debug("WaterlevelExporter.addWKmsData");

        NumberFormat kmf = getKmFormatter();
        NumberFormat wf  = getWFormatter();
        NumberFormat qf  = getQFormatter();

        int      size   = wqkms.size();
        double[] result = new double[3];

        FLYSArtifact flys       = (FLYSArtifact) master;
        Gauge        gauge      = FLYSUtils.getGauge(flys);
        String       gaugeName  = gauge.getName();
        String       desc       = "";
        String       notinrange = msg(
            CSV_NOT_IN_GAUGE_RANGE,
            DEFAULT_CSV_NOT_IN_GAUGE_RANGE);

        double a = gauge.getRange().getA().doubleValue();
        double b = gauge.getRange().getB().doubleValue();

        if (flys instanceof WINFOArtifact && isQ) {
            desc = getCSVRowTitle((WINFOArtifact)flys, wqkms);
        }
        else if (!isQ) {
            Double value = FLYSUtils.getValueFromWQ(wqkms);
            desc         = value != null
                ? Formatter.getWaterlevelW(context).format(value) : null;
        }

        long startTime = System.currentTimeMillis();

        for (int i = 0; i < size; i ++) {
            result = wqkms.get(i, result);

            if (atGauge) {
                source.addData(new String[] {
                    kmf.format(result[2]),
                    wf.format(result[0]),
                    qf.format(result[1]),
                    desc,
                    FLYSUtils.getLocationDescription(flys, result[2]),
                    result[2] >= a && result[2] <= b
                        ? gaugeName
                        : notinrange
                });
            }
            else {
                source.addData(new String[] {
                    kmf.format(result[2]),
                    wf.format(result[0]),
                    qf.format(result[1]),
                    desc,
                    FLYSUtils.getLocationDescription(flys, result[2]),
                    result[2] >= a && result[2] <= b
                        ? gaugeName
                        : notinrange
                });
            }
        }

        long stopTime = System.currentTimeMillis();

        if (logger.isDebugEnabled()) {
            logger.debug("Writing PDF data took " +
                (float)(stopTime-startTime)/1000f + " secs.");
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org