teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
teichmann@5863: * Software engineering by Intevation GmbH
teichmann@5863: *
teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3)
teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the
teichmann@5994: * documentation coming with Dive4Elements River for details.
teichmann@5863: */
teichmann@5863:
teichmann@5831: package org.dive4elements.river.exports;
ingo@389:
ingo@446: import java.io.IOException;
ingo@389: import java.io.OutputStream;
ingo@2045: import java.text.DateFormat;
ingo@418: import java.text.NumberFormat;
ingo@389: import java.util.ArrayList;
gernotbelger@9082: import java.util.Date;
raimund@2176: import java.util.HashMap;
ingo@389: import java.util.List;
ingo@2045: import java.util.Locale;
gernotbelger@9082: import java.util.Map;
ingo@2035: import java.util.regex.Matcher;
ingo@2035: import java.util.regex.Pattern;
ingo@389:
ingo@389: import org.apache.log4j.Logger;
aheinecke@6601: import org.dive4elements.artifacts.Artifact;
teichmann@5831: import org.dive4elements.artifacts.CallMeta;
teichmann@5831: import org.dive4elements.artifacts.common.utils.Config;
gernotbelger@9082: import org.dive4elements.river.artifacts.D4EArtifact;
gernotbelger@9082: import org.dive4elements.river.artifacts.FixationArtifact;
gernotbelger@9082: import org.dive4elements.river.artifacts.StaticWQKmsArtifact;
gernotbelger@9082: import org.dive4elements.river.artifacts.WINFOArtifact;
teichmann@5831: import org.dive4elements.river.artifacts.access.FixRealizingAccess;
felix@7620: import org.dive4elements.river.artifacts.access.IsOfficialAccess;
teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess;
teichmann@5831: import org.dive4elements.river.artifacts.model.CalculationResult;
gernotbelger@9082: import org.dive4elements.river.artifacts.model.ConstantWQKms;
teichmann@5831: import org.dive4elements.river.artifacts.model.Segment;
gernotbelger@9082: import org.dive4elements.river.artifacts.model.WKmsJRDataSource;
teichmann@5831: import org.dive4elements.river.artifacts.model.WQCKms;
teichmann@5831: import org.dive4elements.river.artifacts.model.WQKms;
gernotbelger@9082: import org.dive4elements.river.artifacts.model.WQKmsResult;
aheinecke@7604: import org.dive4elements.river.artifacts.model.WstLine;
teichmann@5831: import org.dive4elements.river.artifacts.resources.Resources;
gernotbelger@9082: import org.dive4elements.river.model.Gauge;
gernotbelger@9082: import org.dive4elements.river.utils.Formatter;
teichmann@5865: import org.dive4elements.river.utils.RiverUtils;
teichmann@5865: import org.dive4elements.river.utils.RiverUtils.WQ_MODE;
gernotbelger@9082:
gernotbelger@9082: import au.com.bytecode.opencsv.CSVWriter;
gernotbelger@9082: import gnu.trove.TDoubleArrayList;
gernotbelger@9082: import net.sf.jasperreports.engine.JRException;
gernotbelger@9082: import net.sf.jasperreports.engine.JasperExportManager;
gernotbelger@9082: import net.sf.jasperreports.engine.JasperFillManager;
gernotbelger@9082: import net.sf.jasperreports.engine.JasperPrint;
ingo@389:
ingo@389: /**
felix@3252: * Generates different output formats (wst, csv, pdf) of data that resulted from
felix@3252: * a waterlevel computation.
felix@3252: *
ingo@389: * @author Ingo Weinzierl
ingo@389: */
ingo@391: public class WaterlevelExporter extends AbstractExporter {
ingo@389:
gernotbelger@9082: /** The log used in this exporter. */
teichmann@8202: private static Logger log = Logger.getLogger(WaterlevelExporter.class);
ingo@389:
ingo@446: public static final String FACET_WST = "wst";
ingo@446:
aheinecke@6601: /* This should be the same as in the StaticWQKmsArtifact */
aheinecke@6601: public static final String STATICWQKMSNAME = "staticwqkms";
aheinecke@6601:
gernotbelger@9082: public static final String CSV_KM_HEADER = "export.waterlevel.csv.header.km";
ingo@416:
gernotbelger@9082: public static final String CSV_W_HEADER = "export.waterlevel.csv.header.w";
ingo@416:
gernotbelger@9312: public static final String CSV_Q_HEADER = "common.export.csv.header.q";
ingo@2063:
gernotbelger@8932: /**
gernotbelger@8932: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
gernotbelger@8932: */
gernotbelger@8932: @Deprecated
gernotbelger@9082: public static final String CSV_Q_DESC_HEADER = "export.waterlevel.csv.header.q.desc";
ingo@2045:
gernotbelger@8932: /**
gernotbelger@8932: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
gernotbelger@8932: */
gernotbelger@8932: @Deprecated
gernotbelger@9082: public static final String CSV_W_DESC_HEADER = "export.waterlevel.csv.header.w.desc";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_LOCATION_HEADER = "export.waterlevel.csv.header.location";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_GAUGE_HEADER = "export.waterlevel.csv.header.gauge";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_RESULT = "export.waterlevel.csv.meta.result";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_CREATION = "export.waterlevel.csv.meta.creation";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_CALCULATIONBASE = "export.waterlevel.csv.meta.calculationbase";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_RIVER = "export.waterlevel.csv.meta.river";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_RANGE = "export.waterlevel.csv.meta.range";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_GAUGE = "export.waterlevel.csv.meta.gauge";
gernotbelger@9082:
gernotbelger@9323: public static final String CSV_META_Q = "common.export.waterlevel.csv.meta.q";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_META_W = "export.waterlevel.csv.meta.w";
gernotbelger@9082:
gernotbelger@9082: public static final String CSV_NOT_IN_GAUGE_RANGE = "export.waterlevel.csv.not.in.gauge.range";
gernotbelger@9082:
gernotbelger@9082: public static final Pattern NUMBERS_PATTERN = Pattern.compile("\\D*(\\d++.\\d*)\\D*");
gernotbelger@9082:
gernotbelger@9082: public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km";
gernotbelger@9082: public static final String DEFAULT_CSV_W_HEADER = "W [NN + m]";
gernotbelger@9082: public static final String DEFAULT_CSV_Q_HEADER = "Q [m\u00b3/s]";
gernotbelger@8932: /**
gernotbelger@8932: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
gernotbelger@8932: */
gernotbelger@8932: @Deprecated
gernotbelger@9082: public static final String DEFAULT_CSV_Q_DESC_HEADER = "Bezeichnung";
gernotbelger@9082: /**
gernotbelger@9082: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
gernotbelger@9082: */
gernotbelger@9082: @Deprecated
gernotbelger@9082: public static final String DEFAULT_CSV_W_DESC_HEADER = "W/Pegel [cm]";
ingo@2063: public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage";
gernotbelger@9082: public static final String DEFAULT_CSV_GAUGE_HEADER = "Bezugspegel";
gernotbelger@9082: public static final String DEFAULT_CSV_NOT_IN_GAUGE_RANGE = "außerhalb des gewählten Bezugspegels";
ingo@416:
raimund@2176: public static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode";
gernotbelger@9082: public static final String JASPER_FILE = "export.waterlevel.pdf.file";
ingo@416:
gernotbelger@9082: /** The storage that contains all WQKms objects that are calculated. */
ingo@389: protected List data;
ingo@389:
gernotbelger@9082: /** The storage that contains official fixings if available. */
aheinecke@6601: protected List officalFixings;
ingo@389:
teichmann@7077: public WaterlevelExporter() {
gernotbelger@9082: this.data = new ArrayList<>();
ingo@389: }
ingo@389:
ingo@446: @Override
gernotbelger@9082: public void generate() throws IOException {
teichmann@8202: log.debug("WaterlevelExporter.generate");
ingo@446:
gernotbelger@9082: /*
gernotbelger@9082: * Check for official fixings. They should also be included in the
gernotbelger@9082: * export but only the calculation result is added with addData
gernotbelger@9082: */
aheinecke@6601:
gernotbelger@9082: this.officalFixings = new ArrayList<>();
gernotbelger@9082:
gernotbelger@9082: for (final Artifact art : this.collection.getArtifactsByName(STATICWQKMSNAME, this.context)) {
aheinecke@6601: if (art instanceof StaticWQKmsArtifact) {
gernotbelger@9082: final IsOfficialAccess access = new IsOfficialAccess((D4EArtifact) art);
gernotbelger@9082: final StaticWQKmsArtifact sart = (StaticWQKmsArtifact) art;
felix@7620: if (!access.isOfficial()) {
aheinecke@6601: continue;
aheinecke@6601: }
aheinecke@6601:
aheinecke@6601: /* Check that we add the data only once */
gernotbelger@9082: final WQKms toAdd = sart.getWQKms();
gernotbelger@9082: final String newName = toAdd.getName();
aheinecke@6601:
aheinecke@6601: boolean exists = false;
gernotbelger@9082: for (final WQKms wqkm : this.officalFixings) {
gernotbelger@9082: /*
gernotbelger@9082: * The same official fixing could be in two
gernotbelger@9082: * artifacts/outs so let's deduplicate
gernotbelger@9082: */
aheinecke@6601: if (wqkm.getName().equals(newName)) {
aheinecke@6601: exists = true;
aheinecke@6601: }
aheinecke@6601: }
aheinecke@6601: if (!exists) {
gernotbelger@9082: this.officalFixings.add(toAdd);
teichmann@8202: log.debug("Adding additional offical fixing: " + newName);
aheinecke@6601: }
aheinecke@6601: }
aheinecke@6601: }
aheinecke@6601:
gernotbelger@9082: if (this.facet != null && this.facet.equals(AbstractExporter.FACET_CSV)) {
ingo@446: generateCSV();
gernotbelger@9082: } else if (this.facet != null && this.facet.equals(FACET_WST)) {
ingo@446: generateWST();
gernotbelger@9082: } else if (this.facet != null && this.facet.equals(AbstractExporter.FACET_PDF)) {
raimund@2176: generatePDF();
gernotbelger@9082: } else {
ingo@446: throw new IOException("invalid facet for exporter");
ingo@446: }
ingo@446: }
ingo@446:
sascha@701: @Override
sascha@701: protected void addData(Object d) {
sascha@709: if (d instanceof CalculationResult) {
gernotbelger@9082: d = ((CalculationResult) d).getData();
gernotbelger@9082: if (d instanceof WQKms[]) {
gernotbelger@9082: this.data.add((WQKms[]) d);
gernotbelger@9082: } else if (d instanceof WQKmsResult) {
gernotbelger@9082: this.data.add(((WQKmsResult) d).getWQKms());
ingo@3462: }
sascha@701: }
ingo@389: }
ingo@389:
ingo@2035: /**
felix@6576: * Prepare the column titles of waterlevel exports.
ingo@2035: * Titles in this export include the Q value. If a Q value matches a named
ingo@2035: * main value (as HQ100 or MNQ) this named main value should be used as
ingo@2035: * title. This method resets the name of the wqkms object if such
ingo@2035: * named main value fits to the chosen Q.
ingo@2035: *
gernotbelger@9082: * @param winfo
gernotbelger@9082: * A WINFO Artifact.
gernotbelger@9082: * @param wqkms
gernotbelger@9082: * A WQKms object that should be prepared.
ingo@2035: */
gernotbelger@9082: protected String getColumnTitle(final WINFOArtifact winfo, final WQKms wqkms) {
teichmann@8202: log.debug("WaterlevelExporter.getColumnTitle");
ingo@2035:
gernotbelger@9082: final String name = wqkms.getName();
ingo@2035:
teichmann@8202: log.debug("Name of WQKms = '" + name + "'");
ingo@2035:
ingo@2038: if (name.indexOf("W=") >= 0) {
ingo@2038: return name;
ingo@2038: }
ingo@2038:
gernotbelger@9082: final Matcher m = NUMBERS_PATTERN.matcher(name);
ingo@2035:
ingo@2035: if (m.matches()) {
gernotbelger@9082: final String raw = m.group(1);
ingo@2035:
ingo@2035: try {
gernotbelger@9082: final double v = Double.valueOf(raw);
ingo@2035:
teichmann@5865: String nmv = RiverUtils.getNamedMainValue(winfo, v);
ingo@2035:
ingo@2035: if (nmv != null && nmv.length() > 0) {
gernotbelger@9082: nmv = RiverUtils.stripNamedMainValue(nmv);
ingo@2596: nmv += "=" + String.valueOf(v);
teichmann@8202: log.debug("Set named main value '" + nmv + "'");
ingo@2035:
ingo@2038: return nmv;
ingo@2035: }
ingo@2035: }
gernotbelger@9082: catch (final NumberFormatException nfe) {
ingo@2035: // do nothing here
ingo@2035: }
ingo@2035: }
ingo@2038:
ingo@2038: return name;
ingo@2035: }
ingo@2035:
gernotbelger@8932: /**
gernotbelger@8932: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
gernotbelger@8932: */
gernotbelger@8932: @Deprecated
gernotbelger@9082: protected String getCSVRowTitle(final WINFOArtifact winfo, final WQKms wqkms) {
teichmann@8202: log.debug("WaterlevelExporter.prepareNamedValue");
ingo@2087:
gernotbelger@9082: final String name = wqkms.getName();
ingo@2087:
teichmann@8202: log.debug("Name of WQKms = '" + name + "'");
ingo@2087:
gernotbelger@9082: final WQ_MODE wqmode = RiverUtils.getWQMode(winfo);
ingo@2087:
ingo@2087: if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.QGAUGE) {
ingo@2087: return localizeWQKms(winfo, wqkms);
ingo@2087: }
ingo@2087:
gernotbelger@9082: final Double v = wqkms.getRawValue();
ingo@2087:
teichmann@5865: String nmv = RiverUtils.getNamedMainValue(winfo, v);
ingo@2087:
ingo@2087: if (nmv != null && nmv.length() > 0) {
teichmann@5865: nmv = RiverUtils.stripNamedMainValue(nmv);
teichmann@8202: log.debug("Set named main value '" + nmv + "'");
ingo@2087:
ingo@2087: return nmv;
ingo@2087: }
ingo@2087:
ingo@2087: return localizeWQKms(winfo, wqkms);
ingo@2087: }
ingo@2087:
felix@3252: /**
felix@3252: * Get a string like 'W=' or 'Q=' with a number following in localized
felix@3252: * format.
gernotbelger@9082: *
gernotbelger@8932: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
felix@3252: */
gernotbelger@8932: @Deprecated
gernotbelger@9082: protected String localizeWQKms(final WINFOArtifact winfo, final WQKms wqkms) {
gernotbelger@9082: final WQ_MODE wqmode = RiverUtils.getWQMode(winfo);
gernotbelger@9082: final Double rawValue = wqkms.getRawValue();
ingo@2087:
ingo@2087: if (rawValue == null) {
ingo@2087: return wqkms.getName();
ingo@2087: }
ingo@2087:
gernotbelger@9082: final NumberFormat nf = Formatter.getRawFormatter(this.context);
ingo@2087:
ingo@2087: if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.WGAUGE) {
ingo@2087: return "W=" + nf.format(rawValue);
gernotbelger@9082: } else {
ingo@2087: return "Q=" + nf.format(rawValue);
ingo@2087: }
ingo@2087: }
ingo@2087:
sascha@701: @Override
gernotbelger@9082: protected void writeCSVData(final CSVWriter writer) {
teichmann@8202: log.info("WaterlevelExporter.writeData");
ingo@389:
gernotbelger@9082: final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master);
gernotbelger@9082: final boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
gernotbelger@9082: final boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
gernotbelger@9082: final RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode((D4EArtifact) this.master);
ingo@2065:
ingo@2045: writeCSVMeta(writer);
ingo@2068: writeCSVHeader(writer, atGauge, isQ);
ingo@416:
aheinecke@6601: Double first = Double.NaN;
aheinecke@6601: Double last = Double.NaN;
aheinecke@6601:
gernotbelger@9082: for (final WQKms[] tmp : this.data) {
gernotbelger@9082: for (final WQKms wqkms : tmp) {
felix@3252: wQKms2CSV(writer, wqkms, atGauge, isQ);
gernotbelger@9082: final double[] firstLast = wqkms.getFirstLastKM();
aheinecke@6601: if (first.isNaN()) {
aheinecke@6601: /* Initialize */
aheinecke@6601: first = firstLast[0];
aheinecke@6601: last = firstLast[1];
aheinecke@6601: }
aheinecke@6601: if (firstLast[0] > firstLast[1]) {
gernotbelger@9082: /*
gernotbelger@9082: * Calculating upstream we assert that it is
aheinecke@6601: * impossible that the direction changes during this
gernotbelger@9082: * loop
gernotbelger@9082: */
aheinecke@6601: first = Math.max(first, firstLast[0]);
aheinecke@6601: last = Math.min(last, firstLast[1]);
aheinecke@6601: } else if (firstLast[0] < firstLast[1]) {
aheinecke@6601: first = Math.min(first, firstLast[0]);
aheinecke@6601: last = Math.max(last, firstLast[1]);
aheinecke@6601: } else {
aheinecke@6601: first = last = firstLast[0];
aheinecke@6601: }
ingo@389: }
ingo@389: }
aheinecke@6601: /* Append the official fixing at the bottom */
gernotbelger@9082: for (final WQKms wqkms : this.officalFixings) {
aheinecke@6603: wQKms2CSV(writer, filterWQKms(wqkms, first, last), atGauge, isQ);
aheinecke@6603: }
aheinecke@6603: }
aheinecke@6601:
gernotbelger@9082: /**
gernotbelger@9082: * Filter a wqkms object to a distance.
aheinecke@6603: *
aheinecke@6603: * To handle upstream / downstream and to limit
aheinecke@6603: * the officialFixings to the calculation distance
aheinecke@6603: * we create a new wqkms object here and fill it only
aheinecke@6603: * with the relevant data.
aheinecke@6603: *
gernotbelger@9082: * @param wqkms:
gernotbelger@9082: * The WQKms Object to filter
gernotbelger@9082: * @param first:
gernotbelger@9082: * The fist kilometer of the range
gernotbelger@9082: * @param last:
gernotbelger@9082: * The last kilometer of the range
aheinecke@6603: *
aheinecke@6603: * @return A new WQKms with the relevant data sorted by direction
aheinecke@6603: */
gernotbelger@9082: private WQKms filterWQKms(final WQKms wqkms, final Double first, final Double last) {
aheinecke@6603: if (first.isNaN() || last.isNaN()) {
teichmann@8202: log.warn("Filtering official fixing without valid first/last.");
aheinecke@6603: return wqkms;
aheinecke@6603: }
gernotbelger@9082: final int firstIdx = first > last ? wqkms.size() - 1 : 0;
gernotbelger@9082: final int lastIdx = first > last ? 0 : wqkms.size() - 1;
gernotbelger@9082: final WQKms filtered = new WQKms(wqkms.size());
aheinecke@6603: filtered.setName(wqkms.getName());
gernotbelger@9082: double[] dp = new double[3];
aheinecke@6603:
aheinecke@6603: if (first > last) {
aheinecke@6603: for (int i = wqkms.size() - 1; i >= 0; i--) {
aheinecke@6603: dp = wqkms.get(i, dp);
aheinecke@6605: if (dp[2] <= first + 1E-5 && dp[2] > last - 1E-5) {
aheinecke@6603: filtered.add(dp[0], dp[1], dp[2]);
aheinecke@6601: }
aheinecke@6601: }
aheinecke@6603: } else {
teichmann@7254: for (int i = 0, N = wqkms.size(); i < N; i++) {
aheinecke@6603: dp = wqkms.get(i, dp);
aheinecke@6605: if (dp[2] < last + 1E-5 && dp[2] > first - 1E-5) {
aheinecke@6603: filtered.add(dp[0], dp[1], dp[2]);
aheinecke@6603: }
aheinecke@6603: }
aheinecke@6601: }
aheinecke@6603: return filtered;
ingo@389: }
aheinecke@6603:
gernotbelger@9082: protected void writeCSVMeta(final CSVWriter writer) {
teichmann@8202: log.info("WaterlevelExporter.writeCSVMeta");
ingo@2045:
teichmann@5865: // TODO use Access instead of RiverUtils
felix@4859:
gernotbelger@9082: final CallMeta meta = this.context.getMeta();
ingo@2045:
gernotbelger@9082: final D4EArtifact flys = (D4EArtifact) this.master;
ingo@2045:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_RESULT, CSV_META_RESULT, new Object[] { RiverUtils.getRivername(flys) }) });
ingo@2045:
gernotbelger@9082: final Locale locale = Resources.getLocale(meta);
gernotbelger@9082: final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
gernotbelger@9082:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_CREATION, CSV_META_CREATION, new Object[] { df.format(new Date()) }) });
gernotbelger@9082:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_CALCULATIONBASE, CSV_META_CALCULATIONBASE, new Object[] { "" }) // TODO what is required
gernotbelger@9082: // at this place?
ingo@2045: });
ingo@2045:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_RIVER, CSV_META_RIVER, new Object[] { RiverUtils.getRivername(flys) }) });
ingo@2045:
gernotbelger@9082: final RangeAccess rangeAccess = new RangeAccess(flys);
gernotbelger@9082: final double[] kms = rangeAccess.getKmRange();
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_RANGE, CSV_META_RANGE, new Object[] { kms[0], kms[kms.length - 1] }) });
ingo@2045:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_GAUGE, CSV_META_GAUGE, new Object[] { RiverUtils.getGaugename(flys) }) });
ingo@2045:
gernotbelger@9040: // TODO: code extracted into WaterlevelDescriptionBuilder, should be used instead.
gernotbelger@9082: final RiverUtils.WQ_MODE wq = RiverUtils.getWQMode(flys);
teichmann@5865: if (wq == RiverUtils.WQ_MODE.QFREE || wq == RiverUtils.WQ_MODE.QGAUGE) {
gernotbelger@9082: final double[] qs = RiverUtils.getQs(flys);
gernotbelger@9082: final RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode(flys);
ingo@2045:
ingo@2423: String data = "";
ingo@2045:
gernotbelger@9082: if ((input == RiverUtils.WQ_INPUT.ADAPTED || input == RiverUtils.WQ_INPUT.RANGE) && qs != null && qs.length > 0) {
ingo@2423: data = String.valueOf(qs[0]);
gernotbelger@9082: data += " - " + String.valueOf(qs[qs.length - 1]);
gernotbelger@9082: } else if (input == RiverUtils.WQ_INPUT.SINGLE && qs != null) {
ingo@2423: data = String.valueOf(qs[0]);
ingo@2423: for (int i = 1; i < qs.length; i++) {
ingo@2423: data += ", " + String.valueOf(qs[i]);
ingo@2423: }
gernotbelger@9082: } else {
teichmann@8202: log.warn("Could not determine Q range!");
ingo@2045: }
ingo@2045:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_Q, CSV_META_Q, new Object[] { data }) });
gernotbelger@9082: } else {
gernotbelger@9082: final double[] ws = RiverUtils.getWs(flys);
ingo@2045:
ingo@2045: String lower = "";
ingo@2045: String upper = "";
ingo@2045:
ingo@2045: if (ws != null && ws.length > 0) {
ingo@2045: lower = String.valueOf(ws[0]);
gernotbelger@9082: upper = String.valueOf(ws[ws.length - 1]);
gernotbelger@9082: } else {
teichmann@8202: log.warn("Could not determine W range!");
ingo@2045: }
ingo@2045:
gernotbelger@9082: writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_W, CSV_META_W, new Object[] { lower, upper }) });
ingo@2045: }
ingo@2045:
ingo@2045: writer.writeNext(new String[] { "" });
ingo@2045: }
ingo@2045:
felix@4979: /**
felix@4979: * Write the header, with different headings depending on whether at a
felix@4979: * gauge or at a location.
felix@4979: */
gernotbelger@9082: protected void writeCSVHeader(final CSVWriter writer, final boolean atGauge, final boolean isQ) {
teichmann@8202: log.info("WaterlevelExporter.writeCSVHeader");
ingo@416:
gernotbelger@9082: final String unit = RiverUtils.getRiver((D4EArtifact) this.master).getWstUnit().getName();
felix@5133:
ingo@2065: if (atGauge) {
gernotbelger@9082: writer.writeNext(new String[] { msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }),
gernotbelger@9082: msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
gernotbelger@9082:
gernotbelger@9082: // FIXME: use WaterlevelDescriptionBuilder instead and also remove all this duplicate code.
gernotbelger@9082: (isQ ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER) : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)),
gernotbelger@9082: msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER), msg(CSV_GAUGE_HEADER, DEFAULT_CSV_GAUGE_HEADER) });
gernotbelger@9082: } else {
gernotbelger@9082: writer.writeNext(new String[] { msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER),
felix@5133: // TODO flys/issue1128 (unit per river)
gernotbelger@9082: msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER),
gernotbelger@9082: msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER) });
ingo@2065: }
ingo@416: }
ingo@416:
felix@5112: /** Linearly search for gauge which is valid at km. */
gernotbelger@9082: private static Gauge findGauge(final double km, final List gauges) {
gernotbelger@9082: for (final Gauge gauge : gauges) {
teichmann@5587: if (gauge.getRange().contains(km)) {
felix@5112: return gauge;
felix@5112: }
felix@5112: }
felix@5112: return null;
felix@5112: }
felix@5112:
gernotbelger@9082: private static Segment findSegment(final double km, final List segments) {
gernotbelger@9082: for (final Segment segment : segments) {
teichmann@5587: if (segment.inside(km)) {
teichmann@5587: return segment;
teichmann@5587: }
teichmann@5587: }
teichmann@5587: return null;
teichmann@5587: }
teichmann@5587:
gernotbelger@9082: private void writeRow4(final CSVWriter writer, final double wqkm[], final D4EArtifact flys) {
gernotbelger@9082: final NumberFormat kmf = getKmFormatter();
gernotbelger@9082: final NumberFormat wf = getWFormatter();
gernotbelger@9082: final NumberFormat qf = getQFormatter();
gernotbelger@9082: writer.writeNext(new String[] { kmf.format(wqkm[2]), wf.format(wqkm[0]), qf.format(RiverUtils.roundQ(wqkm[1])),
gernotbelger@9082: RiverUtils.getLocationDescription(flys, wqkm[2]) });
felix@5112: }
felix@5112:
felix@5112: /** Write an csv-row at gauge location. */
gernotbelger@9082: private void writeRow6(final CSVWriter writer, final double wqkm[], final String wOrQDesc, final D4EArtifact flys, final String gaugeName) {
gernotbelger@9082: final NumberFormat kmf = getKmFormatter();
gernotbelger@9082: final NumberFormat wf = getWFormatter();
gernotbelger@9082: final NumberFormat qf = getQFormatter();
felix@5112:
gernotbelger@9082: writer.writeNext(new String[] { kmf.format(wqkm[2]), wf.format(wqkm[0]), qf.format(RiverUtils.roundQ(wqkm[1])), wOrQDesc,
gernotbelger@9082: RiverUtils.getLocationDescription(flys, wqkm[2]), gaugeName });
felix@5112: }
felix@5112:
gernotbelger@8932: /**
gernotbelger@8932: * @deprecated Use {@link WaterlevelDescriptionBuilder} instead.
gernotbelger@8932: */
gernotbelger@8932: @Deprecated
gernotbelger@9082: private String getDesc(final WQKms wqkms, final boolean isQ) {
gernotbelger@9082: final D4EArtifact flys = (D4EArtifact) this.master;
aheinecke@6606: String colDesc = "";
aheinecke@6606:
aheinecke@6606: if (flys instanceof WINFOArtifact && isQ) {
gernotbelger@9082: colDesc = getCSVRowTitle((WINFOArtifact) flys, wqkms);
gernotbelger@9082: } else if (!isQ) {
gernotbelger@9082: final Double value = RiverUtils.getValueFromWQ(wqkms);
gernotbelger@9082: colDesc = (value != null) ? Formatter.getWaterlevelW(this.context).format(value) : null;
aheinecke@6606: }
aheinecke@6606:
aheinecke@6606: if (flys instanceof WINFOArtifact) {
aheinecke@6606: if (wqkms != null && wqkms.getRawValue() != null) {
gernotbelger@9082: final WINFOArtifact winfo = (WINFOArtifact) flys;
gernotbelger@9082: colDesc = RiverUtils.getNamedMainValue(winfo, wqkms.getRawValue());
aheinecke@6606: // For 'W am Pegel' s
aheinecke@6606: if (colDesc == null) {
gernotbelger@9082: final Double value = RiverUtils.getValueFromWQ(wqkms);
gernotbelger@9082: colDesc = (value != null) ? Formatter.getWaterlevelW(this.context).format(value) : null;
aheinecke@6606: }
aheinecke@6606: }
aheinecke@6606: }
aheinecke@6610: if (colDesc != null) {
gernotbelger@9082: /*
gernotbelger@9082: * Quick hack. Can be removed when database strings are
gernotbelger@9082: * adapted or left in here as it should never be harmful.
gernotbelger@9082: */
aheinecke@6610: colDesc = colDesc.replace("Amtl.Festlegung_", "Amtl. ");
aheinecke@6610: }
aheinecke@6610:
aheinecke@6606: return colDesc == null ? "" : colDesc;
aheinecke@6606: }
felix@5112:
felix@3252: /**
felix@3252: * Write "rows" of csv data from wqkms with writer.
felix@3252: */
gernotbelger@9082: protected void wQKms2CSV(final CSVWriter writer, final WQKms wqkms, final boolean atGauge, final boolean isQ) {
teichmann@8202: log.debug("WaterlevelExporter.wQKms2CSV");
ingo@389:
teichmann@4836: // Skip constant data.
teichmann@4836: if (wqkms instanceof ConstantWQKms) {
teichmann@4836: return;
teichmann@4836: }
teichmann@4836:
gernotbelger@9082: final NumberFormat kmf = getKmFormatter();
gernotbelger@9082: final NumberFormat wf = getWFormatter();
gernotbelger@9082: final NumberFormat qf = getQFormatter();
ingo@418:
gernotbelger@9082: final int size = wqkms.size();
ingo@389: double[] result = new double[3];
ingo@389:
gernotbelger@9082: final D4EArtifact flys = (D4EArtifact) this.master;
gernotbelger@9082: final RangeAccess rangeAccess = new RangeAccess(flys);
tom@8759:
gernotbelger@9082: final List gauges = RiverUtils.getGauges(flys);
tom@8759:
gernotbelger@9082: final Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange());
gernotbelger@9082:
gernotbelger@9082: final String gaugeName = gauge.getName();
gernotbelger@9082: String desc = "";
gernotbelger@9082: final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE);
aheinecke@6606: List segments = null;
aheinecke@6606: boolean isFixRealize = false;
ingo@2066:
gernotbelger@9082: final double a = gauge.getRange().getA().doubleValue();
gernotbelger@9082: final double b = gauge.getRange().getB().doubleValue();
gernotbelger@9082: final long startTime = System.currentTimeMillis();
sascha@2144:
aheinecke@6606: desc = getDesc(wqkms, isQ);
aheinecke@6606:
aheinecke@6606: if (flys instanceof FixationArtifact) {
felix@5103: // Get W/Q input per gauge for this case.
gernotbelger@9082: final FixRealizingAccess fixAccess = new FixRealizingAccess(flys);
felix@5103: segments = fixAccess.getSegments();
teichmann@5587: if (segments != null && !segments.isEmpty()) {
felix@5112: isFixRealize = true;
felix@5112: }
felix@5103: }
sascha@2612:
teichmann@5587: if (atGauge) { // "At gauge" needs more output.
ingo@389:
teichmann@5587: // Kms tend to be close together so caching the last sector
teichmann@5587: // is a good time saving heuristic.
teichmann@5587: Segment lastSegment = null;
gernotbelger@9082: Gauge lastGauge = null;
teichmann@5587:
gernotbelger@9082: final NumberFormat nf = Formatter.getFormatter(this.context.getMeta(), 0, 0);
teichmann@5587:
teichmann@5587: for (int i = 0; i < size; ++i) {
teichmann@5587: result = wqkms.get(i, result);
gernotbelger@9082: final double km = result[2];
teichmann@5587:
teichmann@5587: if (segments != null) {
gernotbelger@9082: final Segment found = lastSegment != null && lastSegment.inside(km) ? lastSegment : findSegment(km, segments);
teichmann@5587:
teichmann@5587: if (found != null) {
aheinecke@6606: desc = nf.format(found.getValues()[0]);
felix@5103: }
teichmann@5587: lastSegment = found;
felix@5103: }
felix@5103:
felix@5112: String gaugeN;
felix@5112: if (isFixRealize) {
gernotbelger@9082: final Gauge found = lastGauge != null && lastGauge.getRange().contains(km) ? lastGauge : findGauge(km, gauges);
teichmann@5587:
teichmann@5587: gaugeN = found != null ? found.getName() : notinrange;
teichmann@5587: lastGauge = found;
gernotbelger@9082: } else {
felix@4979: // TODO issue1114: Take correct gauge
gernotbelger@9082: gaugeN = km >= a && km <= b ? gaugeName : notinrange;
felix@5112: }
aheinecke@6606: writeRow6(writer, result, desc, flys, gaugeN);
ingo@2065: }
gernotbelger@9082: } else { // Not at gauge.
teichmann@5587: for (int i = 0; i < size; ++i) {
teichmann@5587: result = wqkms.get(i, result);
felix@5112: writeRow4(writer, result, flys);
ingo@2065: }
ingo@389: }
sascha@2144:
gernotbelger@9082: final long stopTime = System.currentTimeMillis();
sascha@2144:
teichmann@8202: if (log.isDebugEnabled()) {
gernotbelger@9082: log.debug("Writing CSV took " + (stopTime - startTime) / 1000f + " secs.");
sascha@2144: }
ingo@389: }
ingo@418:
ingo@418: /**
ingo@446: * Generates the output in WST format.
ingo@446: */
gernotbelger@9082: protected void generateWST() throws IOException {
teichmann@8202: log.info("WaterlevelExporter.generateWST");
ingo@446:
gernotbelger@9082: final int cols = this.data.get(0).length + this.officalFixings.size();
gernotbelger@9082: final WstWriter writer = new WstWriter(cols);
ingo@446:
ingo@446: writeWSTData(writer);
ingo@446:
gernotbelger@9082: writer.write(this.out);
ingo@446: }
ingo@446:
gernotbelger@9082: protected void writeWSTData(final WstWriter writer) {
teichmann@8202: log.debug("WaterlevelExporter.writeWSTData");
ingo@446:
ingo@749: double[] result = new double[4];
ingo@446:
gernotbelger@9082: for (final WQKms[] tmp : this.data) {
gernotbelger@9082: for (final WQKms wqkms : tmp) {
rrenkert@5105: if (wqkms instanceof ConstantWQKms) {
rrenkert@5105: continue;
rrenkert@5105: }
gernotbelger@9082: final int size = wqkms != null ? wqkms.size() : 0;
ingo@446:
ingo@450: addWSTColumn(writer, wqkms);
ingo@447:
ingo@446: for (int i = 0; i < size; i++) {
ingo@446: result = wqkms.get(i, result);
ingo@446:
ingo@446: writer.add(result);
ingo@446: }
ingo@749:
ingo@749: if (wqkms instanceof WQCKms) {
ingo@749: addWSTColumn(writer, wqkms);
ingo@749:
ingo@749: for (int c = 0; c < size; c++) {
ingo@749: result = wqkms.get(c, result);
ingo@749:
ingo@749: writer.addCorrected(result);
ingo@749: }
ingo@749: }
ingo@446: }
ingo@446: }
aheinecke@7604:
aheinecke@7604: // Append the official fixing interpolated to the calculation steps
aheinecke@7604: //
aheinecke@7604: // There was some confusion how to implement this. see flys/issue1620
aheinecke@7604: // for details.
gernotbelger@9082: for (final WQKms wqkms : this.officalFixings) {
aheinecke@7604: // To add some spaces here or to add them in the writer,..
aheinecke@7615: writer.addColumn(getDesc(wqkms, true));
aheinecke@7604:
aheinecke@7604: // Get all lines from the calculation
gernotbelger@9082: final Map calcLines = writer.getLines();
aheinecke@7604:
aheinecke@7604: // All KM values where we have a point for
gernotbelger@9082: final TDoubleArrayList officialKms = wqkms.allKms();
aheinecke@7604:
gernotbelger@9082: for (final Map.Entry entry : calcLines.entrySet()) {
aheinecke@7604: // Bad for perfomance but the user can wait a bit for WST
aheinecke@7604: // so lets not spend time optimizing too much,.. *hides*
gernotbelger@9082: final double km = entry.getKey().doubleValue();
gernotbelger@9082: final int idx = officialKms.indexOf(km);
aheinecke@7604: if (idx != -1) {
aheinecke@7614: entry.getValue().add(wqkms.getW(idx), wqkms.getQ(idx));
aheinecke@7604: }
aheinecke@7604: }
aheinecke@7604: }
ingo@446: }
ingo@446:
ingo@2038: /**
felix@6576: * Register a new column at writer. The name /
ingo@2038: * title of the column depends on the Q or W value of wqkms. If a Q
ingo@2038: * was selected and the Q fits to a named main value, the title is set to
ingo@2038: * the named main value. Otherwise, the name returned by
ingo@2038: * WQKms.getName() is set.
ingo@2038: *
gernotbelger@9082: * @param writer
gernotbelger@9082: * The WstWriter.
gernotbelger@9082: * @param wqkms
gernotbelger@9082: * The new WST column.
ingo@2038: */
gernotbelger@9082: protected void addWSTColumn(final WstWriter writer, final WQKms wqkms) {
teichmann@4836: if (wqkms instanceof ConstantWQKms) {
teichmann@4836: return;
teichmann@4836: }
gernotbelger@9082: if (this.master instanceof WINFOArtifact) {
gernotbelger@9082: writer.addColumn(getColumnTitle((WINFOArtifact) this.master, wqkms));
gernotbelger@9082: } else {
ingo@2038: writer.addColumn(wqkms.getName());
ingo@2038: }
ingo@450: }
ingo@450:
raimund@2176: @Override
gernotbelger@9082: protected void writePDF(final OutputStream out) {
teichmann@8202: log.debug("write PDF");
gernotbelger@9082: final WKmsJRDataSource source = createJRData();
raimund@2185:
gernotbelger@9082: final String jasperFile = // "/jasper/waterlevel_en.jasper";
gernotbelger@9082: Resources.getMsg(this.context.getMeta(), JASPER_FILE, "/jasper/waterlevel_en.jasper");
gernotbelger@9082: final String confPath = Config.getConfigDirectory().toString();
raimund@2185:
gernotbelger@9082: final Map parameters = new HashMap();
raimund@2176: parameters.put("ReportTitle", "Exported Data");
raimund@2176: try {
gernotbelger@9082: final JasperPrint print = JasperFillManager.fillReport(confPath + jasperFile, parameters, source);
raimund@2176: JasperExportManager.exportReportToPdfStream(print, out);
raimund@2176: }
gernotbelger@9082: catch (final JRException je) {
teichmann@8202: log.warn("Error generating PDF Report!", je);
raimund@2176: }
raimund@2176: }
raimund@2176:
raimund@2176: protected WKmsJRDataSource createJRData() {
gernotbelger@9082: final WKmsJRDataSource source = new WKmsJRDataSource();
raimund@2176:
gernotbelger@9082: final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master);
gernotbelger@9082: final boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE;
gernotbelger@9082: final boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE;
raimund@2176:
aheinecke@6608: Double first = Double.NaN;
aheinecke@6608: Double last = Double.NaN;
aheinecke@6608:
raimund@2176: addMetaData(source);
gernotbelger@9082: for (final WQKms[] tmp : this.data) {
gernotbelger@9082: for (final WQKms wqkms : tmp) {
raimund@2176: addWKmsData(wqkms, atGauge, isQ, source);
gernotbelger@9082: final double[] firstLast = wqkms.getFirstLastKM();
aheinecke@6608: if (first.isNaN()) {
aheinecke@6608: /* Initialize */
aheinecke@6608: first = firstLast[0];
aheinecke@6608: last = firstLast[1];
aheinecke@6608: }
aheinecke@6608: if (firstLast[0] > firstLast[1]) {
gernotbelger@9082: /*
gernotbelger@9082: * Calculating upstream we assert that it is
aheinecke@6608: * impossible that the direction changes during this
gernotbelger@9082: * loop
gernotbelger@9082: */
aheinecke@6608: first = Math.max(first, firstLast[0]);
aheinecke@6608: last = Math.min(last, firstLast[1]);
aheinecke@6608: } else if (firstLast[0] < firstLast[1]) {
aheinecke@6608: first = Math.min(first, firstLast[0]);
aheinecke@6608: last = Math.max(last, firstLast[1]);
aheinecke@6608: } else {
aheinecke@6608: first = last = firstLast[0];
aheinecke@6608: }
raimund@2176: }
raimund@2176: }
aheinecke@6608:
aheinecke@6608: /* Append the official fixing at the bottom */
gernotbelger@9082: for (final WQKms wqkms : this.officalFixings) {
aheinecke@6608: addWKmsData(filterWQKms(wqkms, first, last), atGauge, isQ, source);
aheinecke@6608: }
raimund@2176: return source;
raimund@2176: }
raimund@2176:
gernotbelger@9082: protected void addMetaData(final WKmsJRDataSource source) {
gernotbelger@9082: final CallMeta meta = this.context.getMeta();
raimund@2176:
gernotbelger@9082: final D4EArtifact flys = (D4EArtifact) this.master;
raimund@2176:
gernotbelger@9082: source.addMetaData("river", RiverUtils.getRivername(flys));
gernotbelger@9082:
gernotbelger@9082: final Locale locale = Resources.getLocale(meta);
gernotbelger@9082: final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
gernotbelger@9082: final NumberFormat kmf = getKmFormatter();
raimund@2176:
raimund@2176: source.addMetaData("date", df.format(new Date()));
raimund@2176:
gernotbelger@9082: final RangeAccess rangeAccess = new RangeAccess(flys);
gernotbelger@9082: final double[] kms = rangeAccess.getKmRange();
gernotbelger@9082: source.addMetaData("range", kmf.format(kms[0]) + " - " + kmf.format(kms[kms.length - 1]));
raimund@2176:
teichmann@5865: source.addMetaData("gauge", RiverUtils.getGaugename(flys));
raimund@2176:
gernotbelger@9082: source.addMetaData("calculation", Resources.getMsg(locale, PDF_HEADER_MODE, "Waterlevel"));
raimund@2176: }
raimund@2176:
gernotbelger@9082: protected void addWKmsData(final WQKms wqkms, final boolean atGauge, final boolean isQ, final WKmsJRDataSource source) {
teichmann@8202: log.debug("WaterlevelExporter.addWKmsData");
raimund@2176:
teichmann@4836: // Skip constant data.
teichmann@4836: if (wqkms instanceof ConstantWQKms) {
teichmann@4836: return;
teichmann@4836: }
teichmann@4836:
gernotbelger@9082: final NumberFormat kmf = getKmFormatter();
gernotbelger@9082: final NumberFormat wf = getWFormatter();
gernotbelger@9082: final NumberFormat qf = getQFormatter();
raimund@2176:
gernotbelger@9082: final int size = wqkms.size();
raimund@2176: double[] result = new double[3];
raimund@2176:
gernotbelger@9082: final D4EArtifact flys = (D4EArtifact) this.master;
gernotbelger@9082: final RangeAccess rangeAccess = new RangeAccess(flys);
tom@8759:
gernotbelger@9082: final Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange());
raimund@2176:
gernotbelger@9082: final String gaugeName = gauge.getName();
gernotbelger@9082: String desc = "";
gernotbelger@9082: final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE);
gernotbelger@9082:
gernotbelger@9082: final double a = gauge.getRange().getA().doubleValue();
gernotbelger@9082: final double b = gauge.getRange().getB().doubleValue();
raimund@2176:
aheinecke@6606: desc = getDesc(wqkms, isQ);
gernotbelger@9082: final long startTime = System.currentTimeMillis();
raimund@2176:
gernotbelger@9082: for (int i = 0; i < size; i++) {
raimund@2176: result = wqkms.get(i, result);
raimund@2176:
raimund@2176: if (atGauge) {
gernotbelger@9082: source.addData(new String[] { kmf.format(result[2]), wf.format(result[0]), qf.format(RiverUtils.roundQ(result[1])), desc,
gernotbelger@9082: RiverUtils.getLocationDescription(flys, result[2]), result[2] >= a && result[2] <= b ? gaugeName : notinrange });
gernotbelger@9082: } else {
gernotbelger@9082: source.addData(new String[] { kmf.format(result[2]), wf.format(result[0]), qf.format(RiverUtils.roundQ(result[1])), desc,
gernotbelger@9082: RiverUtils.getLocationDescription(flys, result[2]), result[2] >= a && result[2] <= b ? gaugeName : notinrange });
raimund@2176: }
raimund@2176: }
raimund@2176:
gernotbelger@9082: final long stopTime = System.currentTimeMillis();
raimund@2176:
teichmann@8202: if (log.isDebugEnabled()) {
gernotbelger@9082: log.debug("Writing PDF data took " + (stopTime - startTime) / 1000f + " secs.");
raimund@2176: }
raimund@2176: }
ingo@389: }
ingo@389: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :