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; gernotbelger@9460: import org.dive4elements.river.artifacts.AbstractFixBunduArtifact; gernotbelger@9082: import org.dive4elements.river.artifacts.D4EArtifact; 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; gernotbelger@9486: import org.dive4elements.river.artifacts.common.DefaultCalculationResults; gernotbelger@9486: import org.dive4elements.river.artifacts.common.ExportContextPDF; gernotbelger@9486: import org.dive4elements.river.artifacts.common.GeneralResultType; gernotbelger@9486: import org.dive4elements.river.artifacts.common.JasperDesigner; gernotbelger@9486: import org.dive4elements.river.artifacts.common.JasperReporter; gernotbelger@9486: import org.dive4elements.river.artifacts.common.MetaAndTableJRDataSource; teichmann@5831: import org.dive4elements.river.artifacts.model.CalculationResult; gernotbelger@9082: import org.dive4elements.river.artifacts.model.ConstantWQKms; gernotbelger@9486: import org.dive4elements.river.artifacts.model.DischargeTables; 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@9486: import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; gernotbelger@9486: import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; gernotbelger@9486: import org.dive4elements.river.model.DischargeTable; 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; 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: gernotbelger@9457: private static final String FACET_WST = "wst"; ingo@446: aheinecke@6601: /* This should be the same as in the StaticWQKmsArtifact */ gernotbelger@9457: private 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@9457: private static final String CSV_META_RESULT = "export.waterlevel.csv.meta.result"; gernotbelger@9082: gernotbelger@9457: private static final String CSV_META_CREATION = "export.waterlevel.csv.meta.creation"; gernotbelger@9082: gernotbelger@9457: private static final String CSV_META_CALCULATIONBASE = "export.waterlevel.csv.meta.calculationbase"; gernotbelger@9082: gernotbelger@9457: private static final String CSV_META_RIVER = "export.waterlevel.csv.meta.river"; gernotbelger@9082: gernotbelger@9457: private static final String CSV_META_RANGE = "export.waterlevel.csv.meta.range"; gernotbelger@9457: gernotbelger@9457: private static final String CSV_META_GAUGE = "export.waterlevel.csv.meta.gauge"; gernotbelger@9457: gernotbelger@9457: private static final String CSV_META_Q = "common.export.waterlevel.csv.meta.q"; gernotbelger@9457: gernotbelger@9457: private 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@9457: private 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: gernotbelger@9457: protected static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode"; ingo@416: gernotbelger@9082: /** The storage that contains all WQKms objects that are calculated. */ gernotbelger@9457: public List data; ingo@389: gernotbelger@9082: /** The storage that contains official fixings if available. */ gernotbelger@9457: public List officalFixings; ingo@389: gernotbelger@9486: private final Map gaugeQ_W_Map = new HashMap<>(); gernotbelger@9486: 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@9457: public 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@9457: public 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@9457: public 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: gernotbelger@9486: private final boolean isQ() { gernotbelger@9485: final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master); gernotbelger@9485: return mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; gernotbelger@9459: } gernotbelger@9459: sascha@701: @Override gernotbelger@9457: public void writeCSVData(final CSVWriter writer) { teichmann@8202: log.info("WaterlevelExporter.writeData"); 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@9485: final boolean isQ = isQ(); gernotbelger@9459: gernotbelger@9082: final RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode((D4EArtifact) this.master); 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@9457: public final 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@9457: public 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@9538: final String gaugeName = RiverUtils.getGaugename(flys); gernotbelger@9538: writer.writeNext(new String[] { gernotbelger@9538: Resources.getMsg(meta, CSV_META_GAUGE, CSV_META_GAUGE, new Object[] { gaugeName != null ? gaugeName : Resources.getMsg(meta, "-") }) }); 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@9485: protected void writeRow4(final CSVWriter writer, final double wqkm[], final D4EArtifact flys, final Gauge gauge, final boolean isQ) { 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@9485: protected void writeRow6(final CSVWriter writer, final double wqkm[], final String wOrQDesc, final D4EArtifact flys, final String gaugeName, gernotbelger@9554: final String wAtGauge, final boolean isQ) { 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@9485: 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@9457: public final 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: gernotbelger@9486: private List getSegments(final D4EArtifact flys) { gernotbelger@9486: if (flys instanceof AbstractFixBunduArtifact) { gernotbelger@9486: // Get W/Q input per gauge for this case. gernotbelger@9486: final FixRealizingAccess fixAccess = new FixRealizingAccess(flys); gernotbelger@9486: return fixAccess.getSegments(); gernotbelger@9486: gernotbelger@9486: } gernotbelger@9486: return null; gernotbelger@9486: } gernotbelger@9486: 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 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: mschaefer@9536: Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange()); mschaefer@9536: mschaefer@9536: // REMARK gauge may be null when rangeAccess starts outside any gauge range mschaefer@9536: if (gauge == null) mschaefer@9536: gauge = rangeAccess.getRiver().determineRefGauge(wqkms.getKms(), rangeAccess.isRange()); gernotbelger@9082: gernotbelger@9554: final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); gernotbelger@9554: final String gaugeName = gauge != null ? gauge.getName() : notinrange; mschaefer@9536: gernotbelger@9554: final double a = gauge != null ? gauge.getRange().getA().doubleValue() : Double.NaN; gernotbelger@9554: final double b = gauge != null ? gauge.getRange().getB().doubleValue() : Double.NaN; ingo@2066: gernotbelger@9082: final long startTime = System.currentTimeMillis(); sascha@2144: gernotbelger@9554: String desc = getDesc(wqkms, isQ); aheinecke@6606: gernotbelger@9486: final List segments = getSegments(flys); gernotbelger@9554: final boolean isFixRealize = isFixrealize(segments); 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@9554: gernotbelger@9554: final double station = result[2]; gernotbelger@9554: final double q = result[1]; teichmann@5587: teichmann@5587: if (segments != null) { gernotbelger@9554: final Segment found = lastSegment != null && lastSegment.inside(station) ? lastSegment : findSegment(station, 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; gernotbelger@9554: final String wAtGauge; felix@5112: if (isFixRealize) { gernotbelger@9554: final Gauge found = lastGauge != null && lastGauge.getRange().contains(station) ? lastGauge : findGauge(station, gauges); teichmann@5587: teichmann@5587: gaugeN = found != null ? found.getName() : notinrange; teichmann@5587: lastGauge = found; gernotbelger@9554: gernotbelger@9554: wAtGauge = this.getWaterlevel(q, found); // THIS IS NEW (and makes common super method difficult) gernotbelger@9082: } else { felix@4979: // TODO issue1114: Take correct gauge gernotbelger@9554: gaugeN = station >= a && station <= b ? gaugeName : notinrange; gernotbelger@9554: gernotbelger@9554: wAtGauge = ""; felix@5112: } gernotbelger@9554: gernotbelger@9554: writeRow6(writer, result, desc, flys, gaugeN, wAtGauge, isQ); ingo@2065: } gernotbelger@9082: } else { // Not at gauge. teichmann@5587: for (int i = 0; i < size; ++i) { teichmann@5587: result = wqkms.get(i, result); gernotbelger@9485: writeRow4(writer, result, flys, gauge, isQ); 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: gernotbelger@9554: private boolean isFixrealize(final List segments) { gernotbelger@9554: boolean isFixRealize = false; gernotbelger@9554: if (segments != null && !segments.isEmpty()) { gernotbelger@9554: isFixRealize = true; gernotbelger@9554: } gernotbelger@9554: return isFixRealize; gernotbelger@9554: } gernotbelger@9554: ingo@418: /** ingo@446: * Generates the output in WST format. ingo@446: */ gernotbelger@9457: public 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@9457: public 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: gernotbelger@9486: @Override gernotbelger@9486: protected void writePDF(final OutputStream out) { gernotbelger@9486: log.debug("write PDF"); raimund@2185: gernotbelger@9486: final boolean isQ = isQ(); gernotbelger@9486: final MetaAndTableJRDataSource source = new MetaAndTableJRDataSource(); gernotbelger@9554: gernotbelger@9554: final boolean isFixationAnalysis = this.master instanceof AbstractFixBunduArtifact; gernotbelger@9554: final boolean doWaterlevelAtGaugeOutput = isQ && isFixationAnalysis; gernotbelger@9554: gernotbelger@9554: final String jasperFile = doWaterlevelAtGaugeOutput ? "/jasper/templates/fix_waterlevel.jrxml" : "/jasper/templates/waterlevel.jrxml"; gernotbelger@9486: gernotbelger@9486: ((D4EArtifact) this.master).getData("calculation.mode"); gernotbelger@9486: if ((this.master instanceof WINFOArtifact)) { gernotbelger@9554: addMetaData(source, "calc.surface.curve", isQ); // Wasserspiegellage gernotbelger@9554: } else if (this.master instanceof AbstractFixBunduArtifact) { gernotbelger@9554: addMetaData(source, ((AbstractFixBunduArtifact) this.master).getCalculationModeString(), isQ); gernotbelger@9486: } gernotbelger@9486: raimund@2176: try { gernotbelger@9486: final List sorted = getRows(); // Custom Result could be nice, too... gernotbelger@9486: for (final String[] list : sorted) { gernotbelger@9486: source.addData(list); gernotbelger@9486: } gernotbelger@9486: gernotbelger@9486: final JasperReporter reporter = new JasperReporter(); gernotbelger@9486: gernotbelger@9486: final JasperDesigner d = reporter.addReport(jasperFile, source); gernotbelger@9486: d.removeColumn("delete"); // I don't want to mess with getRows(), so I prefer deleting the unwanted row directly in the report. gernotbelger@9486: gernotbelger@9486: reporter.exportPDF(this.out); raimund@2176: } gernotbelger@9082: catch (final JRException je) { teichmann@8202: log.warn("Error generating PDF Report!", je); raimund@2176: } gernotbelger@9486: raimund@2176: } raimund@2176: gernotbelger@9486: private List getRows() { gernotbelger@9486: final List list = new ArrayList<>(); gernotbelger@9486: final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master); gernotbelger@9486: final boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; gernotbelger@9486: final boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; gernotbelger@9486: gernotbelger@9486: Double first = Double.NaN; gernotbelger@9486: Double last = Double.NaN; gernotbelger@9486: gernotbelger@9486: for (final WQKms[] tmp : this.data) { gernotbelger@9486: for (final WQKms wqkms : tmp) { gernotbelger@9486: list.addAll(getRows2(wqkms, atGauge, isQ)); gernotbelger@9486: final double[] firstLast = wqkms.getFirstLastKM(); gernotbelger@9486: if (first.isNaN()) { gernotbelger@9486: /* Initialize */ gernotbelger@9486: first = firstLast[0]; gernotbelger@9486: last = firstLast[1]; gernotbelger@9486: } gernotbelger@9486: if (firstLast[0] > firstLast[1]) { gernotbelger@9486: /* gernotbelger@9486: * Calculating upstream we assert that it is gernotbelger@9486: * impossible that the direction changes during this gernotbelger@9486: * loop gernotbelger@9486: */ gernotbelger@9486: first = Math.max(first, firstLast[0]); gernotbelger@9486: last = Math.min(last, firstLast[1]); gernotbelger@9486: } else if (firstLast[0] < firstLast[1]) { gernotbelger@9486: first = Math.min(first, firstLast[0]); gernotbelger@9486: last = Math.max(last, firstLast[1]); gernotbelger@9486: } else { gernotbelger@9486: first = last = firstLast[0]; gernotbelger@9486: } gernotbelger@9486: } gernotbelger@9486: } gernotbelger@9486: gernotbelger@9486: /* Append the official fixing at the bottom */ gernotbelger@9486: for (final WQKms wqkms : this.officalFixings) { gernotbelger@9486: list.addAll(getRows2(filterWQKms(wqkms, first, last), atGauge, isQ)); gernotbelger@9486: } gernotbelger@9486: return list; gernotbelger@9486: } gernotbelger@9486: gernotbelger@9486: private List getRows2(final WQKms wqkms, final boolean atGauge, final boolean isQ) { gernotbelger@9486: log.debug("WaterlevelExporter.addWKmsData"); // OLD CODE :-/ gernotbelger@9486: gernotbelger@9486: final List list = new ArrayList<>(); gernotbelger@9486: // Skip constant data. gernotbelger@9486: if (wqkms instanceof ConstantWQKms) { gernotbelger@9486: return null; gernotbelger@9486: } gernotbelger@9486: gernotbelger@9486: final NumberFormat kmf = getKmFormatter(); gernotbelger@9486: final NumberFormat wf = getWFormatter(); gernotbelger@9486: final NumberFormat qf = getQFormatter(); gernotbelger@9486: gernotbelger@9486: final int size = wqkms.size(); gernotbelger@9486: double[] result = new double[3]; gernotbelger@9486: gernotbelger@9486: final D4EArtifact flys = (D4EArtifact) this.master; gernotbelger@9486: final RangeAccess rangeAccess = new RangeAccess(flys); gernotbelger@9486: gernotbelger@9554: final List gauges = RiverUtils.getGauges(flys); gernotbelger@9554: gernotbelger@9554: Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange()); gernotbelger@9554: // REMARK gauge may be null when rangeAccess starts outside any gauge range gernotbelger@9554: if (gauge == null) gernotbelger@9554: gauge = rangeAccess.getRiver().determineRefGauge(wqkms.getKms(), rangeAccess.isRange()); gernotbelger@9486: gernotbelger@9486: final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); gernotbelger@9538: final String gaugeName = gauge != null ? gauge.getName() : notinrange; gernotbelger@9486: gernotbelger@9554: final double a = gauge != null ? gauge.getRange().getA().doubleValue() : Double.NaN; gernotbelger@9554: final double b = gauge != null ? gauge.getRange().getB().doubleValue() : Double.NaN; gernotbelger@9554: gernotbelger@9486: final WaterlevelDescriptionBuilder wldb = new WaterlevelDescriptionBuilder(flys, this.context); gernotbelger@9486: gernotbelger@9554: String desc = wldb.getDesc(wqkms);// class getDesc(wqkms, isQ); gernotbelger@9486: gernotbelger@9486: Segment lastSegment = null; gernotbelger@9554: gernotbelger@9486: final List segments = getSegments(flys); gernotbelger@9486: gernotbelger@9554: final boolean isFixRealize = isFixrealize(segments); gernotbelger@9554: gernotbelger@9486: final NumberFormat nf = Formatter.getFormatter(this.context.getMeta(), 0, 0); gernotbelger@9486: gernotbelger@9554: Gauge lastGauge = null; gernotbelger@9554: gernotbelger@9486: for (int i = 0; i < size; ++i) { gernotbelger@9486: result = wqkms.get(i, result); gernotbelger@9554: gernotbelger@9554: final double station = result[2]; gernotbelger@9554: final double q = result[1]; gernotbelger@9554: final double w = result[0]; gernotbelger@9486: gernotbelger@9486: if (segments != null) { gernotbelger@9554: final Segment found = lastSegment != null && lastSegment.inside(station) ? lastSegment : findSegment(station, segments); gernotbelger@9486: gernotbelger@9486: if (found != null) { gernotbelger@9486: desc = nf.format(found.getValues()[0]); gernotbelger@9486: } gernotbelger@9486: lastSegment = found; gernotbelger@9486: } gernotbelger@9486: gernotbelger@9554: String gaugeN; gernotbelger@9554: final String wAtGauge; gernotbelger@9554: if (isFixRealize) { gernotbelger@9554: final Gauge found = lastGauge != null && lastGauge.getRange().contains(station) ? lastGauge : findGauge(station, gauges); gernotbelger@9554: gernotbelger@9554: gaugeN = found != null ? found.getName() : notinrange; gernotbelger@9554: lastGauge = found; gernotbelger@9554: gernotbelger@9554: wAtGauge = this.getWaterlevel(q, found); // THIS IS NEW (and makes common super method difficult) gernotbelger@9486: } else { gernotbelger@9554: // TODO issue1114: Take correct gauge gernotbelger@9554: gaugeN = station >= a && station <= b ? gaugeName : notinrange; gernotbelger@9554: wAtGauge = ""; gernotbelger@9486: } gernotbelger@9486: gernotbelger@9554: if (atGauge) { gernotbelger@9554: list.add(new String[] { kmf.format(station), wf.format(w), wAtGauge, qf.format(RiverUtils.roundQ(q)), desc, gernotbelger@9554: RiverUtils.getLocationDescription(flys, station), gaugeN }); gernotbelger@9554: } else { gernotbelger@9554: list.add(new String[] { kmf.format(station), wf.format(w), wAtGauge, qf.format(RiverUtils.roundQ(q)), desc, gernotbelger@9554: RiverUtils.getLocationDescription(flys, station), gaugeN }); gernotbelger@9554: } gernotbelger@9486: } gernotbelger@9486: gernotbelger@9486: return list; gernotbelger@9486: } gernotbelger@9486: gernotbelger@9554: protected final void addMetaData(final MetaAndTableJRDataSource source, final String calculation, final boolean isQ) { gernotbelger@9486: final D4EArtifact flys = (D4EArtifact) this.master; gernotbelger@9486: final String user = CalculationUtils.findArtifactUser(this.context, flys); gernotbelger@9486: final RangeAccess ra = new RangeAccess(flys); gernotbelger@9486: final RiverInfo ri = new RiverInfo(ra.getRiver()); gernotbelger@9486: gernotbelger@9486: final DefaultCalculationResults results = new DefaultCalculationResults(msg(calculation), user, ri, ra.getRange()); gernotbelger@9486: final ExportContextPDF contextPdf = new ExportContextPDF(this.context, results); gernotbelger@9486: contextPdf.addJRMetaDataDefaults(source); gernotbelger@9486: contextPdf.addJRMetaDataForModules(source); gernotbelger@9486: gernotbelger@9486: /* column headings */ gernotbelger@9486: contextPdf.addJRMetadata(source, "station_header", GeneralResultType.station); gernotbelger@9486: contextPdf.addJRMetadata(source, "fix_w", msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { ri.getWstUnit() })); gernotbelger@9486: contextPdf.addJRMetadata(source, "w_at_gauge_header", msg("fix.export.csv.w_at_gauge")); gernotbelger@9486: contextPdf.addJRMetadata(source, "fix_q", msg(CSV_Q_HEADER)); gernotbelger@9554: gernotbelger@9486: contextPdf.addJRMetadata(source, "waterlevel_name_header", msg("common.export.csv.header.mainvalue_label")); gernotbelger@9486: contextPdf.addJRMetadata(source, "location_header", msg("common.export.csv.header.location")); gernotbelger@9486: gernotbelger@9554: // FIXME: use WaterlevelDescriptionBuilder instead and also remove all this duplicate code. gernotbelger@9554: final String waterlevelOrBezeichnung = (isQ ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER) : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)); gernotbelger@9554: contextPdf.addJRMetadata(source, "w_at_gauge_header_2", waterlevelOrBezeichnung); gernotbelger@9554: gernotbelger@9554: // msg("export.waterlevel.csv.header.w.desc")); (old. delete) gernotbelger@9486: gernotbelger@9486: contextPdf.addJRMetadata(source, "location_header", msg("common.export.csv.header.location")); gernotbelger@9486: contextPdf.addJRMetadata(source, "gauge_header", msg("common.export.csv.header.gauge")); gernotbelger@9457: gernotbelger@9457: } gernotbelger@9457: gernotbelger@9457: private 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@9457: public 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: gernotbelger@9457: source.addMetaData("w_at_gauge_header", Resources.getMsg(meta, "fix.export.csv.w_at_gauge")); // dürfte kein Problem sein für Vorlagen, die kein gernotbelger@9457: // "w_at_gauge" gernotbelger@9457: // haben gernotbelger@9457: 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: } gernotbelger@9457: 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: } gernotbelger@9457: gernotbelger@9486: protected final String getWaterlevel(final double discharge, final Gauge gauge) { gernotbelger@9562: final NumberFormat formatter = Formatter.getIntegerFormatter(this.context); gernotbelger@9486: final Double waterlevel = this.getWforGaugeAndQ(gauge, discharge); gernotbelger@9486: if (waterlevel != null) gernotbelger@9486: return formatter.format(waterlevel); gernotbelger@9486: return ""; gernotbelger@9486: } gernotbelger@9486: gernotbelger@9486: private Double getWforGaugeAndQ(final Gauge gauge, final double q) { gernotbelger@9486: gernotbelger@9538: final String key = gauge != null ? gauge.getName() + String.valueOf(q) : null; gernotbelger@9538: if (!this.gaugeQ_W_Map.containsKey(key) && key != null) { gernotbelger@9486: d@9638: // (Pos 19.1 b) d@9638: final DischargeTable dt = gauge.fetchRecentDischargeTable(); gernotbelger@9486: final double[][] table = DischargeTables.loadDischargeTableValues(dt); gernotbelger@9486: gernotbelger@9486: final double[] qs = DischargeTables.getWsForQ(table, q); gernotbelger@9486: gernotbelger@9486: if (qs != null && qs.length > 0) { gernotbelger@9486: this.gaugeQ_W_Map.put(key, qs[0]); gernotbelger@9486: } gernotbelger@9486: } gernotbelger@9486: return this.gaugeQ_W_Map.get(key); gernotbelger@9486: } gernotbelger@9554: }