Mercurial > dive4elements > river
view artifacts/src/main/java/org/dive4elements/river/exports/WaterlevelExporter.java @ 9638:6c1ebf2220f5
# 19b (check for usages of WaterlevelExporter.getWforGaugeAndQ) -> cleanup: removing calc.extreme.curve
author | dnt_bjoernsen <d.tironi@bjoernsen.de> |
---|---|
date | Thu, 31 Oct 2019 17:37:53 +0100 |
parents | 2b1626fa4a95 |
children |
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde * Software engineering by Intevation GmbH * * This file is Free Software under the GNU AGPL (>=v3) * and comes with ABSOLUTELY NO WARRANTY! Check out the * documentation coming with Dive4Elements River for details. */ package org.dive4elements.river.exports; import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.log4j.Logger; import org.dive4elements.artifacts.Artifact; import org.dive4elements.artifacts.CallMeta; import org.dive4elements.river.artifacts.AbstractFixBunduArtifact; import org.dive4elements.river.artifacts.D4EArtifact; import org.dive4elements.river.artifacts.StaticWQKmsArtifact; import org.dive4elements.river.artifacts.WINFOArtifact; import org.dive4elements.river.artifacts.access.FixRealizingAccess; import org.dive4elements.river.artifacts.access.IsOfficialAccess; import org.dive4elements.river.artifacts.access.RangeAccess; import org.dive4elements.river.artifacts.common.DefaultCalculationResults; import org.dive4elements.river.artifacts.common.ExportContextPDF; import org.dive4elements.river.artifacts.common.GeneralResultType; import org.dive4elements.river.artifacts.common.JasperDesigner; import org.dive4elements.river.artifacts.common.JasperReporter; import org.dive4elements.river.artifacts.common.MetaAndTableJRDataSource; import org.dive4elements.river.artifacts.model.CalculationResult; import org.dive4elements.river.artifacts.model.ConstantWQKms; import org.dive4elements.river.artifacts.model.DischargeTables; import org.dive4elements.river.artifacts.model.Segment; import org.dive4elements.river.artifacts.model.WKmsJRDataSource; import org.dive4elements.river.artifacts.model.WQCKms; import org.dive4elements.river.artifacts.model.WQKms; import org.dive4elements.river.artifacts.model.WQKmsResult; import org.dive4elements.river.artifacts.model.WstLine; import org.dive4elements.river.artifacts.resources.Resources; import org.dive4elements.river.artifacts.sinfo.util.CalculationUtils; import org.dive4elements.river.artifacts.sinfo.util.RiverInfo; import org.dive4elements.river.model.DischargeTable; import org.dive4elements.river.model.Gauge; import org.dive4elements.river.utils.Formatter; import org.dive4elements.river.utils.RiverUtils; import org.dive4elements.river.utils.RiverUtils.WQ_MODE; import au.com.bytecode.opencsv.CSVWriter; import gnu.trove.TDoubleArrayList; import net.sf.jasperreports.engine.JRException; /** * Generates different output formats (wst, csv, pdf) of data that resulted from * a waterlevel computation. * * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public class WaterlevelExporter extends AbstractExporter { /** The log used in this exporter. */ private static Logger log = Logger.getLogger(WaterlevelExporter.class); private static final String FACET_WST = "wst"; /* This should be the same as in the StaticWQKmsArtifact */ private static final String STATICWQKMSNAME = "staticwqkms"; public static final String CSV_KM_HEADER = "export.waterlevel.csv.header.km"; public static final String CSV_W_HEADER = "export.waterlevel.csv.header.w"; public static final String CSV_Q_HEADER = "common.export.csv.header.q"; /** * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public static final String CSV_Q_DESC_HEADER = "export.waterlevel.csv.header.q.desc"; /** * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public static final String CSV_W_DESC_HEADER = "export.waterlevel.csv.header.w.desc"; public static final String CSV_LOCATION_HEADER = "export.waterlevel.csv.header.location"; public static final String CSV_GAUGE_HEADER = "export.waterlevel.csv.header.gauge"; private static final String CSV_META_RESULT = "export.waterlevel.csv.meta.result"; private static final String CSV_META_CREATION = "export.waterlevel.csv.meta.creation"; private static final String CSV_META_CALCULATIONBASE = "export.waterlevel.csv.meta.calculationbase"; private static final String CSV_META_RIVER = "export.waterlevel.csv.meta.river"; private static final String CSV_META_RANGE = "export.waterlevel.csv.meta.range"; private static final String CSV_META_GAUGE = "export.waterlevel.csv.meta.gauge"; private static final String CSV_META_Q = "common.export.waterlevel.csv.meta.q"; private static final String CSV_META_W = "export.waterlevel.csv.meta.w"; public static final String CSV_NOT_IN_GAUGE_RANGE = "export.waterlevel.csv.not.in.gauge.range"; private static final Pattern NUMBERS_PATTERN = Pattern.compile("\\D*(\\d++.\\d*)\\D*"); public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km"; public static final String DEFAULT_CSV_W_HEADER = "W [NN + m]"; public static final String DEFAULT_CSV_Q_HEADER = "Q [m\u00b3/s]"; /** * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public static final String DEFAULT_CSV_Q_DESC_HEADER = "Bezeichnung"; /** * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public static final String DEFAULT_CSV_W_DESC_HEADER = "W/Pegel [cm]"; public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage"; public static final String DEFAULT_CSV_GAUGE_HEADER = "Bezugspegel"; public static final String DEFAULT_CSV_NOT_IN_GAUGE_RANGE = "außerhalb des gewählten Bezugspegels"; protected static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode"; /** The storage that contains all WQKms objects that are calculated. */ public List<WQKms[]> data; /** The storage that contains official fixings if available. */ public List<WQKms> officalFixings; private final Map<String, Double> gaugeQ_W_Map = new HashMap<>(); public WaterlevelExporter() { this.data = new ArrayList<>(); } @Override public void generate() throws IOException { log.debug("WaterlevelExporter.generate"); /* * Check for official fixings. They should also be included in the * export but only the calculation result is added with addData */ this.officalFixings = new ArrayList<>(); for (final Artifact art : this.collection.getArtifactsByName(STATICWQKMSNAME, this.context)) { if (art instanceof StaticWQKmsArtifact) { final IsOfficialAccess access = new IsOfficialAccess((D4EArtifact) art); final StaticWQKmsArtifact sart = (StaticWQKmsArtifact) art; if (!access.isOfficial()) { continue; } /* Check that we add the data only once */ final WQKms toAdd = sart.getWQKms(); final String newName = toAdd.getName(); boolean exists = false; for (final WQKms wqkm : this.officalFixings) { /* * The same official fixing could be in two * artifacts/outs so let's deduplicate */ if (wqkm.getName().equals(newName)) { exists = true; } } if (!exists) { this.officalFixings.add(toAdd); log.debug("Adding additional offical fixing: " + newName); } } } if (this.facet != null && this.facet.equals(AbstractExporter.FACET_CSV)) { generateCSV(); } else if (this.facet != null && this.facet.equals(FACET_WST)) { generateWST(); } else if (this.facet != null && this.facet.equals(AbstractExporter.FACET_PDF)) { generatePDF(); } else { throw new IOException("invalid facet for exporter"); } } @Override protected void addData(Object d) { if (d instanceof CalculationResult) { d = ((CalculationResult) d).getData(); if (d instanceof WQKms[]) { this.data.add((WQKms[]) d); } else if (d instanceof WQKmsResult) { this.data.add(((WQKmsResult) d).getWQKms()); } } } /** * Prepare the column titles of waterlevel exports. * Titles in this export include the Q value. If a Q value matches a named * main value (as HQ100 or MNQ) this named main value should be used as * title. This method resets the name of the <i>wqkms</i> object if such * named main value fits to the chosen Q. * * @param winfo * A WINFO Artifact. * @param wqkms * A WQKms object that should be prepared. */ public String getColumnTitle(final WINFOArtifact winfo, final WQKms wqkms) { log.debug("WaterlevelExporter.getColumnTitle"); final String name = wqkms.getName(); log.debug("Name of WQKms = '" + name + "'"); if (name.indexOf("W=") >= 0) { return name; } final Matcher m = NUMBERS_PATTERN.matcher(name); if (m.matches()) { final String raw = m.group(1); try { final double v = Double.valueOf(raw); String nmv = RiverUtils.getNamedMainValue(winfo, v); if (nmv != null && nmv.length() > 0) { nmv = RiverUtils.stripNamedMainValue(nmv); nmv += "=" + String.valueOf(v); log.debug("Set named main value '" + nmv + "'"); return nmv; } } catch (final NumberFormatException nfe) { // do nothing here } } return name; } /** * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public String getCSVRowTitle(final WINFOArtifact winfo, final WQKms wqkms) { log.debug("WaterlevelExporter.prepareNamedValue"); final String name = wqkms.getName(); log.debug("Name of WQKms = '" + name + "'"); final WQ_MODE wqmode = RiverUtils.getWQMode(winfo); if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.QGAUGE) { return localizeWQKms(winfo, wqkms); } final Double v = wqkms.getRawValue(); String nmv = RiverUtils.getNamedMainValue(winfo, v); if (nmv != null && nmv.length() > 0) { nmv = RiverUtils.stripNamedMainValue(nmv); log.debug("Set named main value '" + nmv + "'"); return nmv; } return localizeWQKms(winfo, wqkms); } /** * Get a string like 'W=' or 'Q=' with a number following in localized * format. * * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public String localizeWQKms(final WINFOArtifact winfo, final WQKms wqkms) { final WQ_MODE wqmode = RiverUtils.getWQMode(winfo); final Double rawValue = wqkms.getRawValue(); if (rawValue == null) { return wqkms.getName(); } final NumberFormat nf = Formatter.getRawFormatter(this.context); if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.WGAUGE) { return "W=" + nf.format(rawValue); } else { return "Q=" + nf.format(rawValue); } } private final boolean isQ() { final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master); return mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; } @Override public void writeCSVData(final CSVWriter writer) { log.info("WaterlevelExporter.writeData"); final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master); final boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; final boolean isQ = isQ(); final RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode((D4EArtifact) this.master); writeCSVMeta(writer); writeCSVHeader(writer, atGauge, isQ); Double first = Double.NaN; Double last = Double.NaN; for (final WQKms[] tmp : this.data) { for (final WQKms wqkms : tmp) { wQKms2CSV(writer, wqkms, atGauge, isQ); final double[] firstLast = wqkms.getFirstLastKM(); if (first.isNaN()) { /* Initialize */ first = firstLast[0]; last = firstLast[1]; } if (firstLast[0] > firstLast[1]) { /* * Calculating upstream we assert that it is * impossible that the direction changes during this * loop */ first = Math.max(first, firstLast[0]); last = Math.min(last, firstLast[1]); } else if (firstLast[0] < firstLast[1]) { first = Math.min(first, firstLast[0]); last = Math.max(last, firstLast[1]); } else { first = last = firstLast[0]; } } } /* Append the official fixing at the bottom */ for (final WQKms wqkms : this.officalFixings) { wQKms2CSV(writer, filterWQKms(wqkms, first, last), atGauge, isQ); } } /** * Filter a wqkms object to a distance. * * To handle upstream / downstream and to limit * the officialFixings to the calculation distance * we create a new wqkms object here and fill it only * with the relevant data. * * @param wqkms: * The WQKms Object to filter * @param first: * The fist kilometer of the range * @param last: * The last kilometer of the range * * @return A new WQKms with the relevant data sorted by direction */ public final WQKms filterWQKms(final WQKms wqkms, final Double first, final Double last) { if (first.isNaN() || last.isNaN()) { log.warn("Filtering official fixing without valid first/last."); return wqkms; } final int firstIdx = first > last ? wqkms.size() - 1 : 0; final int lastIdx = first > last ? 0 : wqkms.size() - 1; final WQKms filtered = new WQKms(wqkms.size()); filtered.setName(wqkms.getName()); double[] dp = new double[3]; if (first > last) { for (int i = wqkms.size() - 1; i >= 0; i--) { dp = wqkms.get(i, dp); if (dp[2] <= first + 1E-5 && dp[2] > last - 1E-5) { filtered.add(dp[0], dp[1], dp[2]); } } } else { for (int i = 0, N = wqkms.size(); i < N; i++) { dp = wqkms.get(i, dp); if (dp[2] < last + 1E-5 && dp[2] > first - 1E-5) { filtered.add(dp[0], dp[1], dp[2]); } } } return filtered; } public void writeCSVMeta(final CSVWriter writer) { log.info("WaterlevelExporter.writeCSVMeta"); // TODO use Access instead of RiverUtils final CallMeta meta = this.context.getMeta(); final D4EArtifact flys = (D4EArtifact) this.master; writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_RESULT, CSV_META_RESULT, new Object[] { RiverUtils.getRivername(flys) }) }); final Locale locale = Resources.getLocale(meta); final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_CREATION, CSV_META_CREATION, new Object[] { df.format(new Date()) }) }); writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_CALCULATIONBASE, CSV_META_CALCULATIONBASE, new Object[] { "" }) // TODO what is required // at this place? }); writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_RIVER, CSV_META_RIVER, new Object[] { RiverUtils.getRivername(flys) }) }); final RangeAccess rangeAccess = new RangeAccess(flys); final double[] kms = rangeAccess.getKmRange(); writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_RANGE, CSV_META_RANGE, new Object[] { kms[0], kms[kms.length - 1] }) }); final String gaugeName = RiverUtils.getGaugename(flys); writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_GAUGE, CSV_META_GAUGE, new Object[] { gaugeName != null ? gaugeName : Resources.getMsg(meta, "-") }) }); // TODO: code extracted into WaterlevelDescriptionBuilder, should be used instead. final RiverUtils.WQ_MODE wq = RiverUtils.getWQMode(flys); if (wq == RiverUtils.WQ_MODE.QFREE || wq == RiverUtils.WQ_MODE.QGAUGE) { final double[] qs = RiverUtils.getQs(flys); final RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode(flys); String data = ""; if ((input == RiverUtils.WQ_INPUT.ADAPTED || input == RiverUtils.WQ_INPUT.RANGE) && qs != null && qs.length > 0) { data = String.valueOf(qs[0]); data += " - " + String.valueOf(qs[qs.length - 1]); } else if (input == RiverUtils.WQ_INPUT.SINGLE && qs != null) { data = String.valueOf(qs[0]); for (int i = 1; i < qs.length; i++) { data += ", " + String.valueOf(qs[i]); } } else { log.warn("Could not determine Q range!"); } writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_Q, CSV_META_Q, new Object[] { data }) }); } else { final double[] ws = RiverUtils.getWs(flys); String lower = ""; String upper = ""; if (ws != null && ws.length > 0) { lower = String.valueOf(ws[0]); upper = String.valueOf(ws[ws.length - 1]); } else { log.warn("Could not determine W range!"); } writer.writeNext(new String[] { Resources.getMsg(meta, CSV_META_W, CSV_META_W, new Object[] { lower, upper }) }); } writer.writeNext(new String[] { "" }); } /** * Write the header, with different headings depending on whether at a * gauge or at a location. */ protected void writeCSVHeader(final CSVWriter writer, final boolean atGauge, final boolean isQ) { log.info("WaterlevelExporter.writeCSVHeader"); final String unit = RiverUtils.getRiver((D4EArtifact) this.master).getWstUnit().getName(); if (atGauge) { writer.writeNext(new String[] { msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER), // FIXME: use WaterlevelDescriptionBuilder instead and also remove all this duplicate code. (isQ ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER) : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)), msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER), msg(CSV_GAUGE_HEADER, DEFAULT_CSV_GAUGE_HEADER) }); } else { writer.writeNext(new String[] { msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), // TODO flys/issue1128 (unit per river) msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER), msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER) }); } } /** Linearly search for gauge which is valid at km. */ private static Gauge findGauge(final double km, final List<Gauge> gauges) { for (final Gauge gauge : gauges) { if (gauge.getRange().contains(km)) { return gauge; } } return null; } private static Segment findSegment(final double km, final List<Segment> segments) { for (final Segment segment : segments) { if (segment.inside(km)) { return segment; } } return null; } protected void writeRow4(final CSVWriter writer, final double wqkm[], final D4EArtifact flys, final Gauge gauge, final boolean isQ) { final NumberFormat kmf = getKmFormatter(); final NumberFormat wf = getWFormatter(); final NumberFormat qf = getQFormatter(); writer.writeNext(new String[] { kmf.format(wqkm[2]), wf.format(wqkm[0]), qf.format(RiverUtils.roundQ(wqkm[1])), RiverUtils.getLocationDescription(flys, wqkm[2]) }); } /** Write an csv-row at gauge location. */ protected void writeRow6(final CSVWriter writer, final double wqkm[], final String wOrQDesc, final D4EArtifact flys, final String gaugeName, final String wAtGauge, final boolean isQ) { final NumberFormat kmf = getKmFormatter(); final NumberFormat wf = getWFormatter(); final NumberFormat qf = getQFormatter(); writer.writeNext(new String[] { kmf.format(wqkm[2]), wf.format(wqkm[0]), qf.format(RiverUtils.roundQ(wqkm[1])), wOrQDesc, RiverUtils.getLocationDescription(flys, wqkm[2]), gaugeName }); } /** * @deprecated Use {@link WaterlevelDescriptionBuilder} instead. */ @Deprecated public final String getDesc(final WQKms wqkms, final boolean isQ) { final D4EArtifact flys = (D4EArtifact) this.master; String colDesc = ""; if (flys instanceof WINFOArtifact && isQ) { colDesc = getCSVRowTitle((WINFOArtifact) flys, wqkms); } else if (!isQ) { final Double value = RiverUtils.getValueFromWQ(wqkms); colDesc = (value != null) ? Formatter.getWaterlevelW(this.context).format(value) : null; } if (flys instanceof WINFOArtifact) { if (wqkms != null && wqkms.getRawValue() != null) { final WINFOArtifact winfo = (WINFOArtifact) flys; colDesc = RiverUtils.getNamedMainValue(winfo, wqkms.getRawValue()); // For 'W am Pegel' s if (colDesc == null) { final Double value = RiverUtils.getValueFromWQ(wqkms); colDesc = (value != null) ? Formatter.getWaterlevelW(this.context).format(value) : null; } } } if (colDesc != null) { /* * Quick hack. Can be removed when database strings are * adapted or left in here as it should never be harmful. */ colDesc = colDesc.replace("Amtl.Festlegung_", "Amtl. "); } return colDesc == null ? "" : colDesc; } private List<Segment> getSegments(final D4EArtifact flys) { if (flys instanceof AbstractFixBunduArtifact) { // Get W/Q input per gauge for this case. final FixRealizingAccess fixAccess = new FixRealizingAccess(flys); return fixAccess.getSegments(); } return null; } /** * Write "rows" of csv data from wqkms with writer. */ protected void wQKms2CSV(final CSVWriter writer, final WQKms wqkms, final boolean atGauge, final boolean isQ) { log.debug("WaterlevelExporter.wQKms2CSV"); // Skip constant data. if (wqkms instanceof ConstantWQKms) { return; } final int size = wqkms.size(); double[] result = new double[3]; final D4EArtifact flys = (D4EArtifact) this.master; final RangeAccess rangeAccess = new RangeAccess(flys); final List<Gauge> gauges = RiverUtils.getGauges(flys); Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange()); // REMARK gauge may be null when rangeAccess starts outside any gauge range if (gauge == null) gauge = rangeAccess.getRiver().determineRefGauge(wqkms.getKms(), rangeAccess.isRange()); final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); final String gaugeName = gauge != null ? gauge.getName() : notinrange; final double a = gauge != null ? gauge.getRange().getA().doubleValue() : Double.NaN; final double b = gauge != null ? gauge.getRange().getB().doubleValue() : Double.NaN; final long startTime = System.currentTimeMillis(); String desc = getDesc(wqkms, isQ); final List<Segment> segments = getSegments(flys); final boolean isFixRealize = isFixrealize(segments); if (atGauge) { // "At gauge" needs more output. // Kms tend to be close together so caching the last sector // is a good time saving heuristic. Segment lastSegment = null; Gauge lastGauge = null; final NumberFormat nf = Formatter.getFormatter(this.context.getMeta(), 0, 0); for (int i = 0; i < size; ++i) { result = wqkms.get(i, result); final double station = result[2]; final double q = result[1]; if (segments != null) { final Segment found = lastSegment != null && lastSegment.inside(station) ? lastSegment : findSegment(station, segments); if (found != null) { desc = nf.format(found.getValues()[0]); } lastSegment = found; } String gaugeN; final String wAtGauge; if (isFixRealize) { final Gauge found = lastGauge != null && lastGauge.getRange().contains(station) ? lastGauge : findGauge(station, gauges); gaugeN = found != null ? found.getName() : notinrange; lastGauge = found; wAtGauge = this.getWaterlevel(q, found); // THIS IS NEW (and makes common super method difficult) } else { // TODO issue1114: Take correct gauge gaugeN = station >= a && station <= b ? gaugeName : notinrange; wAtGauge = ""; } writeRow6(writer, result, desc, flys, gaugeN, wAtGauge, isQ); } } else { // Not at gauge. for (int i = 0; i < size; ++i) { result = wqkms.get(i, result); writeRow4(writer, result, flys, gauge, isQ); } } final long stopTime = System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("Writing CSV took " + (stopTime - startTime) / 1000f + " secs."); } } private boolean isFixrealize(final List<Segment> segments) { boolean isFixRealize = false; if (segments != null && !segments.isEmpty()) { isFixRealize = true; } return isFixRealize; } /** * Generates the output in WST format. */ public void generateWST() throws IOException { log.info("WaterlevelExporter.generateWST"); final int cols = this.data.get(0).length + this.officalFixings.size(); final WstWriter writer = new WstWriter(cols); writeWSTData(writer); writer.write(this.out); } public void writeWSTData(final WstWriter writer) { log.debug("WaterlevelExporter.writeWSTData"); double[] result = new double[4]; for (final WQKms[] tmp : this.data) { for (final WQKms wqkms : tmp) { if (wqkms instanceof ConstantWQKms) { continue; } final int size = wqkms != null ? wqkms.size() : 0; addWSTColumn(writer, wqkms); for (int i = 0; i < size; i++) { result = wqkms.get(i, result); writer.add(result); } if (wqkms instanceof WQCKms) { addWSTColumn(writer, wqkms); for (int c = 0; c < size; c++) { result = wqkms.get(c, result); writer.addCorrected(result); } } } } // Append the official fixing interpolated to the calculation steps // // There was some confusion how to implement this. see flys/issue1620 // for details. for (final WQKms wqkms : this.officalFixings) { // To add some spaces here or to add them in the writer,.. writer.addColumn(getDesc(wqkms, true)); // Get all lines from the calculation final Map<Double, WstLine> calcLines = writer.getLines(); // All KM values where we have a point for final TDoubleArrayList officialKms = wqkms.allKms(); for (final Map.Entry<Double, WstLine> entry : calcLines.entrySet()) { // Bad for perfomance but the user can wait a bit for WST // so lets not spend time optimizing too much,.. *hides* final double km = entry.getKey().doubleValue(); final int idx = officialKms.indexOf(km); if (idx != -1) { entry.getValue().add(wqkms.getW(idx), wqkms.getQ(idx)); } } } } /** * Register a new column at <i>writer</i>. The name / * title of the column depends on the Q or W value of <i>wqkms</i>. If a Q * was selected and the Q fits to a named main value, the title is set to * the named main value. Otherwise, the name returned by * <i>WQKms.getName()</i> is set. * * @param writer * The WstWriter. * @param wqkms * The new WST column. */ protected void addWSTColumn(final WstWriter writer, final WQKms wqkms) { if (wqkms instanceof ConstantWQKms) { return; } if (this.master instanceof WINFOArtifact) { writer.addColumn(getColumnTitle((WINFOArtifact) this.master, wqkms)); } else { writer.addColumn(wqkms.getName()); } } @Override protected void writePDF(final OutputStream out) { log.debug("write PDF"); final boolean isQ = isQ(); final MetaAndTableJRDataSource source = new MetaAndTableJRDataSource(); final boolean isFixationAnalysis = this.master instanceof AbstractFixBunduArtifact; final boolean doWaterlevelAtGaugeOutput = isQ && isFixationAnalysis; final String jasperFile = doWaterlevelAtGaugeOutput ? "/jasper/templates/fix_waterlevel.jrxml" : "/jasper/templates/waterlevel.jrxml"; ((D4EArtifact) this.master).getData("calculation.mode"); if ((this.master instanceof WINFOArtifact)) { addMetaData(source, "calc.surface.curve", isQ); // Wasserspiegellage } else if (this.master instanceof AbstractFixBunduArtifact) { addMetaData(source, ((AbstractFixBunduArtifact) this.master).getCalculationModeString(), isQ); } try { final List<String[]> sorted = getRows(); // Custom Result could be nice, too... for (final String[] list : sorted) { source.addData(list); } final JasperReporter reporter = new JasperReporter(); final JasperDesigner d = reporter.addReport(jasperFile, source); d.removeColumn("delete"); // I don't want to mess with getRows(), so I prefer deleting the unwanted row directly in the report. reporter.exportPDF(this.out); } catch (final JRException je) { log.warn("Error generating PDF Report!", je); } } private List<String[]> getRows() { final List<String[]> list = new ArrayList<>(); final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master); final boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; final boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; Double first = Double.NaN; Double last = Double.NaN; for (final WQKms[] tmp : this.data) { for (final WQKms wqkms : tmp) { list.addAll(getRows2(wqkms, atGauge, isQ)); final double[] firstLast = wqkms.getFirstLastKM(); if (first.isNaN()) { /* Initialize */ first = firstLast[0]; last = firstLast[1]; } if (firstLast[0] > firstLast[1]) { /* * Calculating upstream we assert that it is * impossible that the direction changes during this * loop */ first = Math.max(first, firstLast[0]); last = Math.min(last, firstLast[1]); } else if (firstLast[0] < firstLast[1]) { first = Math.min(first, firstLast[0]); last = Math.max(last, firstLast[1]); } else { first = last = firstLast[0]; } } } /* Append the official fixing at the bottom */ for (final WQKms wqkms : this.officalFixings) { list.addAll(getRows2(filterWQKms(wqkms, first, last), atGauge, isQ)); } return list; } private List<String[]> getRows2(final WQKms wqkms, final boolean atGauge, final boolean isQ) { log.debug("WaterlevelExporter.addWKmsData"); // OLD CODE :-/ final List<String[]> list = new ArrayList<>(); // Skip constant data. if (wqkms instanceof ConstantWQKms) { return null; } final NumberFormat kmf = getKmFormatter(); final NumberFormat wf = getWFormatter(); final NumberFormat qf = getQFormatter(); final int size = wqkms.size(); double[] result = new double[3]; final D4EArtifact flys = (D4EArtifact) this.master; final RangeAccess rangeAccess = new RangeAccess(flys); final List<Gauge> gauges = RiverUtils.getGauges(flys); Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange()); // REMARK gauge may be null when rangeAccess starts outside any gauge range if (gauge == null) gauge = rangeAccess.getRiver().determineRefGauge(wqkms.getKms(), rangeAccess.isRange()); final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); final String gaugeName = gauge != null ? gauge.getName() : notinrange; final double a = gauge != null ? gauge.getRange().getA().doubleValue() : Double.NaN; final double b = gauge != null ? gauge.getRange().getB().doubleValue() : Double.NaN; final WaterlevelDescriptionBuilder wldb = new WaterlevelDescriptionBuilder(flys, this.context); String desc = wldb.getDesc(wqkms);// class getDesc(wqkms, isQ); Segment lastSegment = null; final List<Segment> segments = getSegments(flys); final boolean isFixRealize = isFixrealize(segments); final NumberFormat nf = Formatter.getFormatter(this.context.getMeta(), 0, 0); Gauge lastGauge = null; for (int i = 0; i < size; ++i) { result = wqkms.get(i, result); final double station = result[2]; final double q = result[1]; final double w = result[0]; if (segments != null) { final Segment found = lastSegment != null && lastSegment.inside(station) ? lastSegment : findSegment(station, segments); if (found != null) { desc = nf.format(found.getValues()[0]); } lastSegment = found; } String gaugeN; final String wAtGauge; if (isFixRealize) { final Gauge found = lastGauge != null && lastGauge.getRange().contains(station) ? lastGauge : findGauge(station, gauges); gaugeN = found != null ? found.getName() : notinrange; lastGauge = found; wAtGauge = this.getWaterlevel(q, found); // THIS IS NEW (and makes common super method difficult) } else { // TODO issue1114: Take correct gauge gaugeN = station >= a && station <= b ? gaugeName : notinrange; wAtGauge = ""; } if (atGauge) { list.add(new String[] { kmf.format(station), wf.format(w), wAtGauge, qf.format(RiverUtils.roundQ(q)), desc, RiverUtils.getLocationDescription(flys, station), gaugeN }); } else { list.add(new String[] { kmf.format(station), wf.format(w), wAtGauge, qf.format(RiverUtils.roundQ(q)), desc, RiverUtils.getLocationDescription(flys, station), gaugeN }); } } return list; } protected final void addMetaData(final MetaAndTableJRDataSource source, final String calculation, final boolean isQ) { final D4EArtifact flys = (D4EArtifact) this.master; final String user = CalculationUtils.findArtifactUser(this.context, flys); final RangeAccess ra = new RangeAccess(flys); final RiverInfo ri = new RiverInfo(ra.getRiver()); final DefaultCalculationResults results = new DefaultCalculationResults(msg(calculation), user, ri, ra.getRange()); final ExportContextPDF contextPdf = new ExportContextPDF(this.context, results); contextPdf.addJRMetaDataDefaults(source); contextPdf.addJRMetaDataForModules(source); /* column headings */ contextPdf.addJRMetadata(source, "station_header", GeneralResultType.station); contextPdf.addJRMetadata(source, "fix_w", msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { ri.getWstUnit() })); contextPdf.addJRMetadata(source, "w_at_gauge_header", msg("fix.export.csv.w_at_gauge")); contextPdf.addJRMetadata(source, "fix_q", msg(CSV_Q_HEADER)); contextPdf.addJRMetadata(source, "waterlevel_name_header", msg("common.export.csv.header.mainvalue_label")); contextPdf.addJRMetadata(source, "location_header", msg("common.export.csv.header.location")); // FIXME: use WaterlevelDescriptionBuilder instead and also remove all this duplicate code. final String waterlevelOrBezeichnung = (isQ ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER) : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)); contextPdf.addJRMetadata(source, "w_at_gauge_header_2", waterlevelOrBezeichnung); // msg("export.waterlevel.csv.header.w.desc")); (old. delete) contextPdf.addJRMetadata(source, "location_header", msg("common.export.csv.header.location")); contextPdf.addJRMetadata(source, "gauge_header", msg("common.export.csv.header.gauge")); } private WKmsJRDataSource createJRData() { final WKmsJRDataSource source = new WKmsJRDataSource(); final WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact) this.master); final boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; final boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; Double first = Double.NaN; Double last = Double.NaN; addMetaData(source); for (final WQKms[] tmp : this.data) { for (final WQKms wqkms : tmp) { addWKmsData(wqkms, atGauge, isQ, source); final double[] firstLast = wqkms.getFirstLastKM(); if (first.isNaN()) { /* Initialize */ first = firstLast[0]; last = firstLast[1]; } if (firstLast[0] > firstLast[1]) { /* * Calculating upstream we assert that it is * impossible that the direction changes during this * loop */ first = Math.max(first, firstLast[0]); last = Math.min(last, firstLast[1]); } else if (firstLast[0] < firstLast[1]) { first = Math.min(first, firstLast[0]); last = Math.max(last, firstLast[1]); } else { first = last = firstLast[0]; } } } /* Append the official fixing at the bottom */ for (final WQKms wqkms : this.officalFixings) { addWKmsData(filterWQKms(wqkms, first, last), atGauge, isQ, source); } return source; } public void addMetaData(final WKmsJRDataSource source) { final CallMeta meta = this.context.getMeta(); final D4EArtifact flys = (D4EArtifact) this.master; source.addMetaData("river", RiverUtils.getRivername(flys)); final Locale locale = Resources.getLocale(meta); final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); final NumberFormat kmf = getKmFormatter(); source.addMetaData("date", df.format(new Date())); final RangeAccess rangeAccess = new RangeAccess(flys); final double[] kms = rangeAccess.getKmRange(); source.addMetaData("range", kmf.format(kms[0]) + " - " + kmf.format(kms[kms.length - 1])); 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 // "w_at_gauge" // haben source.addMetaData("gauge", RiverUtils.getGaugename(flys)); source.addMetaData("calculation", Resources.getMsg(locale, PDF_HEADER_MODE, "Waterlevel")); } protected void addWKmsData(final WQKms wqkms, final boolean atGauge, final boolean isQ, final WKmsJRDataSource source) { log.debug("WaterlevelExporter.addWKmsData"); // Skip constant data. if (wqkms instanceof ConstantWQKms) { return; } final NumberFormat kmf = getKmFormatter(); final NumberFormat wf = getWFormatter(); final NumberFormat qf = getQFormatter(); final int size = wqkms.size(); double[] result = new double[3]; final D4EArtifact flys = (D4EArtifact) this.master; final RangeAccess rangeAccess = new RangeAccess(flys); final Gauge gauge = rangeAccess.getRiver().determineRefGauge(rangeAccess.getKmRange(), rangeAccess.isRange()); final String gaugeName = gauge.getName(); String desc = ""; final String notinrange = msg(CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); final double a = gauge.getRange().getA().doubleValue(); final double b = gauge.getRange().getB().doubleValue(); desc = getDesc(wqkms, isQ); final long startTime = System.currentTimeMillis(); for (int i = 0; i < size; i++) { result = wqkms.get(i, result); if (atGauge) { source.addData(new String[] { kmf.format(result[2]), wf.format(result[0]), qf.format(RiverUtils.roundQ(result[1])), desc, RiverUtils.getLocationDescription(flys, result[2]), result[2] >= a && result[2] <= b ? gaugeName : notinrange }); } else { source.addData(new String[] { kmf.format(result[2]), wf.format(result[0]), qf.format(RiverUtils.roundQ(result[1])), desc, RiverUtils.getLocationDescription(flys, result[2]), result[2] >= a && result[2] <= b ? gaugeName : notinrange }); } } final long stopTime = System.currentTimeMillis(); if (log.isDebugEnabled()) { log.debug("Writing PDF data took " + (stopTime - startTime) / 1000f + " secs."); } } protected final String getWaterlevel(final double discharge, final Gauge gauge) { final NumberFormat formatter = Formatter.getIntegerFormatter(this.context); final Double waterlevel = this.getWforGaugeAndQ(gauge, discharge); if (waterlevel != null) return formatter.format(waterlevel); return ""; } private Double getWforGaugeAndQ(final Gauge gauge, final double q) { final String key = gauge != null ? gauge.getName() + String.valueOf(q) : null; if (!this.gaugeQ_W_Map.containsKey(key) && key != null) { // (Pos 19.1 b) final DischargeTable dt = gauge.fetchRecentDischargeTable(); final double[][] table = DischargeTables.loadDischargeTableValues(dt); final double[] qs = DischargeTables.getWsForQ(table, q); if (qs != null && qs.length > 0) { this.gaugeQ_W_Map.put(key, qs[0]); } } return this.gaugeQ_W_Map.get(key); } }