diff flys-artifacts/src/main/java/org/dive4elements/river/exports/WaterlevelExporter.java @ 5831:bd047b71ab37

Repaired internal references
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 12:06:39 +0200
parents flys-artifacts/src/main/java/de/intevation/flys/exports/WaterlevelExporter.java@7b1c5fe4ebf3
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flys-artifacts/src/main/java/org/dive4elements/river/exports/WaterlevelExporter.java	Thu Apr 25 12:06:39 2013 +0200
@@ -0,0 +1,854 @@
+package org.dive4elements.river.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 org.dive4elements.river.artifacts.model.ConstantWQKms;
+
+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 org.dive4elements.artifacts.CallContext;
+import org.dive4elements.artifacts.CallMeta;
+import org.dive4elements.artifacts.common.utils.Config;
+
+import org.dive4elements.river.model.Gauge;
+
+import org.dive4elements.river.artifacts.access.FixRealizingAccess;
+import org.dive4elements.river.artifacts.access.RangeAccess;
+import org.dive4elements.river.artifacts.FixationArtifact;
+import org.dive4elements.river.artifacts.FLYSArtifact;
+import org.dive4elements.river.artifacts.WINFOArtifact;
+import org.dive4elements.river.artifacts.model.CalculationResult;
+import org.dive4elements.river.artifacts.model.Segment;
+import org.dive4elements.river.artifacts.model.WQCKms;
+import org.dive4elements.river.artifacts.model.WQKms;
+import org.dive4elements.river.artifacts.model.WKmsJRDataSource;
+import org.dive4elements.river.artifacts.model.WQKmsResult;
+import org.dive4elements.river.artifacts.resources.Resources;
+
+import org.dive4elements.river.utils.FLYSUtils;
+import org.dive4elements.river.utils.FLYSUtils.WQ_MODE;
+import org.dive4elements.river.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 WQKmsResult) {
+                data.add(((WQKmsResult) 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.getColumnTitle");
+
+        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");
+
+        // TODO use Access instead of FLYSUtils
+
+        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) })
+        });
+
+        RangeAccess rangeAccess = new RangeAccess(flys, null);
+        double[] kms = rangeAccess.getKmRange();
+        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[] { "" });
+    }
+
+
+    /**
+     * Write the header, with different headings depending on whether at a
+     * gauge or at a location.
+     */
+    protected void writeCSVHeader(
+        CSVWriter writer,
+        boolean   atGauge,
+        boolean   isQ
+    ) {
+        logger.info("WaterlevelExporter.writeCSVHeader");
+
+        String unit = FLYSUtils.getRiver((FLYSArtifact) master).getWstUnit().getName();
+
+        if (atGauge) {
+            writer.writeNext(new String[] {
+                msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
+                msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }),
+                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),
+                    // TODO flys/issue1128 (unit per river)
+                msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }),
+                msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
+                msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER)
+            });
+        }
+    }
+
+
+    /** Linearly search for gauge which is valid at km. */
+    private static Gauge findGauge(double km, List<Gauge> gauges) {
+        for (Gauge gauge: gauges) {
+            if (gauge.getRange().contains(km)) {
+                return gauge;
+            }
+        }
+        return null;
+    }
+
+    private static Segment findSegment(double km, List<Segment> segments) {
+        for (Segment segment: segments) {
+            if (segment.inside(km)) {
+                return segment;
+            }
+        }
+        return null;
+    }
+
+
+    private void writeRow4(CSVWriter writer, double wqkm[], FLYSArtifact flys) {
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+       writer.writeNext(new String[] {
+           kmf.format(wqkm[2]),
+           wf.format(wqkm[0]),
+           qf.format(wqkm[1]),
+           FLYSUtils.getLocationDescription(flys, wqkm[2])
+       });
+    }
+
+
+    /** Write an csv-row at gauge location. */
+    private void writeRow6(CSVWriter writer, double wqkm[], String wOrQDesc,
+        FLYSArtifact flys, String gaugeName) {
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        writer.writeNext(new String[] {
+            kmf.format(wqkm[2]),
+            wf.format(wqkm[0]),
+            qf.format(wqkm[1]),
+            wOrQDesc,
+            FLYSUtils.getLocationDescription(flys, wqkm[2]),
+            gaugeName
+        });
+    }
+
+
+    /**
+     * Write "rows" of csv data from wqkms with writer.
+     */
+    protected void wQKms2CSV(
+        CSVWriter writer,
+        WQKms     wqkms,
+        boolean   atGauge,
+        boolean   isQ
+    ) {
+        logger.debug("WaterlevelExporter.wQKms2CSV");
+
+        // Skip constant data.
+        if (wqkms instanceof ConstantWQKms) {
+            return;
+        }
+
+        NumberFormat kmf = getKmFormatter();
+        NumberFormat wf  = getWFormatter();
+        NumberFormat qf  = getQFormatter();
+
+        int      size   = wqkms.size();
+        double[] result = new double[3];
+
+        FLYSArtifact flys       = (FLYSArtifact) master;
+        List<Gauge>  gauges     = FLYSUtils.getGauges(flys);
+        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;
+        List<Segment> segments = null;
+        boolean isFixRealize = false;
+        if (flys instanceof WINFOArtifact) {
+            if (wqkms != null && wqkms.getRawValue() != null) {
+                WINFOArtifact winfo = (WINFOArtifact) flys;
+                colDesc = FLYSUtils.getNamedMainValue(winfo, wqkms.getRawValue());
+            }
+        }
+        else if (flys instanceof FixationArtifact) {
+            // Get W/Q input per gauge for this case.
+            FixRealizingAccess fixAccess = new FixRealizingAccess(flys, getCallContext());
+            segments = fixAccess.getSegments();
+            if (segments != null && !segments.isEmpty()) {
+                isFixRealize = true;
+            }
+        }
+
+        if (atGauge) { // "At gauge" needs more output.
+
+            // Kms tend to be close together so caching the last sector
+            // is a good time saving heuristic.
+            Segment lastSegment = null;
+            Gauge   lastGauge   = null;
+
+            NumberFormat nf =
+                Formatter.getFormatter(context.getMeta(), 0, 0);
+
+            for (int i = 0; i < size; ++i) {
+                result = wqkms.get(i, result);
+                double km = result[2];
+
+                if (segments != null) {
+                    Segment found = lastSegment != null
+                                    && lastSegment.inside(km)
+                        ? lastSegment
+                        : findSegment(km, segments);
+
+                    if (found != null) {
+                        colDesc = nf.format(found.getValues()[0]);
+                    }
+                    lastSegment = found;
+                }
+
+                String gaugeN;
+                if (isFixRealize) {
+                    Gauge found = lastGauge != null
+                                  && lastGauge.getRange().contains(km)
+                        ? lastGauge
+                        : findGauge(km, gauges);
+
+                    gaugeN = found != null ? found.getName() : notinrange;
+                    lastGauge = found;
+                }
+                else {
+                    // TODO issue1114: Take correct gauge
+                    gaugeN = km >= a && km <= b
+                        ? gaugeName
+                        : notinrange;
+                }
+                writeRow6(writer, result, colDesc, flys, gaugeN);
+            }
+        }
+        else { // Not at gauge.
+            for (int i = 0; i < size; ++i) {
+                result = wqkms.get(i, result);
+                writeRow4(writer, result, flys);
+            }
+        }
+
+        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) {
+                if (wqkms instanceof ConstantWQKms) {
+                    continue;
+                }
+                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 (wqkms instanceof ConstantWQKms) {
+            return;
+        }
+        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);
+        }
+    }
+
+    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()));
+
+        RangeAccess rangeAccess = new RangeAccess(flys, null);
+        double[] kms = rangeAccess.getKmRange();
+        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");
+
+        // Skip constant data.
+        if (wqkms instanceof ConstantWQKms) {
+            return;
+        }
+
+        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