view artifacts/src/main/java/org/dive4elements/river/exports/WaterlevelExporter.java @ 6332:f5bb53106ae8

Remove createBarriersLayer and createBarriers The generated mapfiles did not work and were just confusing. This looks like historical cruft that was never deleted. The real barrier mapfiles are created in the Floodmap state
author Andre Heinecke <aheinecke@intevation.de>
date Thu, 13 Jun 2013 17:24:56 +0200
parents a0078e5e3b39
children c59a23183bc0
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

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.D4EArtifact;
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.RiverUtils;
import org.dive4elements.river.utils.RiverUtils.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 = RiverUtils.getNamedMainValue(winfo, v);

                if (nmv != null && nmv.length() > 0) {
                    nmv  = RiverUtils.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 = RiverUtils.getWQMode(winfo);

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

        Double v = wqkms.getRawValue();

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

        if (nmv != null && nmv.length() > 0) {
            nmv = RiverUtils.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   = RiverUtils.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    = RiverUtils.getWQMode((D4EArtifact)master);
        boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
        boolean isQ     = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
        RiverUtils.WQ_INPUT input
            = RiverUtils.getWQInputMode((D4EArtifact)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 RiverUtils

        CallMeta meta = context.getMeta();

        D4EArtifact flys = (D4EArtifact) master;

        writer.writeNext(new String[] {
            Resources.getMsg(
                meta,
                CSV_META_RESULT,
                CSV_META_RESULT,
                new Object[] { RiverUtils.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[] { RiverUtils.getRivername(flys) })
        });

        RangeAccess rangeAccess = new RangeAccess(flys);
        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[] { RiverUtils.getGaugename(flys) })
        });

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

            String data = "";

            if ((input == RiverUtils.WQ_INPUT.ADAPTED ||
                input == RiverUtils.WQ_INPUT.RANGE) &&
                qs != null && qs.length > 0)
            {
                data = String.valueOf(qs[0]);
                data += " - " + String.valueOf(qs[qs.length-1]);
            }
            else if (input == RiverUtils.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 = RiverUtils.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 = RiverUtils.getRiver((D4EArtifact) 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[], D4EArtifact 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]),
           RiverUtils.getLocationDescription(flys, wqkm[2])
       });
    }


    /** Write an csv-row at gauge location. */
    private void writeRow6(CSVWriter writer, double wqkm[], String wOrQDesc,
        D4EArtifact 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,
            RiverUtils.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];

        D4EArtifact flys       = (D4EArtifact) master;
        List<Gauge>  gauges     = RiverUtils.getGauges(flys);
        Gauge        gauge      = RiverUtils.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 = RiverUtils.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 = RiverUtils.getNamedMainValue(winfo, wqkms.getRawValue());
                // For 'W am Pegel' s
                if (colDesc == null) {
                    colDesc = ((D4EArtifact)master).getDataAsString("wq_single");
                }
            }
        }
        else if (flys instanceof FixationArtifact) {
            // Get W/Q input per gauge for this case.
            FixRealizingAccess fixAccess = new FixRealizingAccess(flys);
            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    = RiverUtils.getWQMode((D4EArtifact)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();

        D4EArtifact flys = (D4EArtifact) master;

        source.addMetaData ("river", RiverUtils.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);
        double[] kms = rangeAccess.getKmRange();
        source.addMetaData("range", kms[0] + " - " + kms[kms.length-1]);

        source.addMetaData("gauge", RiverUtils.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];

        D4EArtifact flys       = (D4EArtifact) master;
        Gauge        gauge      = RiverUtils.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 = RiverUtils.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,
                    RiverUtils.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,
                    RiverUtils.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