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; raimund@2176: import java.util.Map; raimund@2176: import java.util.HashMap; ingo@2045: import java.util.Date; ingo@389: import java.util.List; ingo@2045: import java.util.Locale; ingo@2035: import java.util.regex.Matcher; ingo@2035: import java.util.regex.Pattern; ingo@389: ingo@389: import org.apache.log4j.Logger; ingo@389: ingo@389: import au.com.bytecode.opencsv.CSVWriter; ingo@389: aheinecke@7604: import gnu.trove.TDoubleArrayList; aheinecke@7604: aheinecke@7604: import org.apache.commons.math.FunctionEvaluationException; aheinecke@7604: import org.apache.commons.math.analysis.UnivariateRealFunction; aheinecke@7604: aheinecke@7604: import org.apache.commons.math.analysis.interpolation.LinearInterpolator; aheinecke@7604: teichmann@5831: import org.dive4elements.river.artifacts.model.ConstantWQKms; teichmann@4836: raimund@2176: import net.sf.jasperreports.engine.JasperExportManager; raimund@2176: import net.sf.jasperreports.engine.JasperFillManager; raimund@2176: import net.sf.jasperreports.engine.JasperPrint; raimund@2176: import net.sf.jasperreports.engine.JRException; raimund@2176: aheinecke@6601: import org.dive4elements.artifacts.Artifact; teichmann@5831: import org.dive4elements.artifacts.CallMeta; teichmann@5831: import org.dive4elements.artifacts.common.utils.Config; ingo@2066: teichmann@5831: import org.dive4elements.river.model.Gauge; sascha@706: teichmann@5831: import org.dive4elements.river.artifacts.access.FixRealizingAccess; teichmann@5831: import org.dive4elements.river.artifacts.access.RangeAccess; teichmann@5831: import org.dive4elements.river.artifacts.FixationArtifact; teichmann@5867: import org.dive4elements.river.artifacts.D4EArtifact; teichmann@5831: import org.dive4elements.river.artifacts.WINFOArtifact; aheinecke@6601: import org.dive4elements.river.artifacts.StaticWQKmsArtifact; teichmann@5831: import org.dive4elements.river.artifacts.model.CalculationResult; teichmann@5831: import org.dive4elements.river.artifacts.model.Segment; teichmann@5831: import org.dive4elements.river.artifacts.model.WQCKms; teichmann@5831: import org.dive4elements.river.artifacts.model.WQKms; aheinecke@7604: import org.dive4elements.river.artifacts.model.WstLine; teichmann@5831: import org.dive4elements.river.artifacts.model.WKmsJRDataSource; teichmann@5831: import org.dive4elements.river.artifacts.model.WQKmsResult; teichmann@5831: import org.dive4elements.river.artifacts.resources.Resources; teichmann@5831: teichmann@5865: import org.dive4elements.river.utils.RiverUtils; teichmann@5865: import org.dive4elements.river.utils.RiverUtils.WQ_MODE; teichmann@5831: import org.dive4elements.river.utils.Formatter; 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: ingo@389: /** The logger used in this exporter.*/ ingo@389: private static Logger logger = 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: ingo@416: public static final String CSV_KM_HEADER = ingo@416: "export.waterlevel.csv.header.km"; ingo@416: ingo@416: public static final String CSV_W_HEADER = ingo@416: "export.waterlevel.csv.header.w"; ingo@416: ingo@416: public static final String CSV_Q_HEADER = ingo@416: "export.waterlevel.csv.header.q"; ingo@416: ingo@2063: public static final String CSV_Q_DESC_HEADER = ingo@2063: "export.waterlevel.csv.header.q.desc"; ingo@2063: ingo@2068: public static final String CSV_W_DESC_HEADER = ingo@2068: "export.waterlevel.csv.header.w.desc"; ingo@2068: ingo@2063: public static final String CSV_LOCATION_HEADER = ingo@2063: "export.waterlevel.csv.header.location"; ingo@2063: ingo@2063: public static final String CSV_GAUGE_HEADER = ingo@2063: "export.waterlevel.csv.header.gauge"; ingo@2063: ingo@2045: public static final String CSV_META_RESULT = ingo@2045: "export.waterlevel.csv.meta.result"; ingo@2045: ingo@2045: public static final String CSV_META_CREATION = ingo@2045: "export.waterlevel.csv.meta.creation"; ingo@2045: ingo@2045: public static final String CSV_META_CALCULATIONBASE = ingo@2045: "export.waterlevel.csv.meta.calculationbase"; ingo@2045: ingo@2045: public static final String CSV_META_RIVER = ingo@2045: "export.waterlevel.csv.meta.river"; ingo@2045: ingo@2045: public static final String CSV_META_RANGE = ingo@2045: "export.waterlevel.csv.meta.range"; ingo@2045: ingo@2045: public static final String CSV_META_GAUGE = ingo@2045: "export.waterlevel.csv.meta.gauge"; ingo@2045: ingo@2045: public static final String CSV_META_Q = ingo@2045: "export.waterlevel.csv.meta.q"; ingo@2045: ingo@2045: public static final String CSV_META_W = ingo@2045: "export.waterlevel.csv.meta.w"; ingo@2045: ingo@2066: public static final String CSV_NOT_IN_GAUGE_RANGE = ingo@2066: "export.waterlevel.csv.not.in.gauge.range"; ingo@2066: ingo@2035: public static final Pattern NUMBERS_PATTERN = ingo@2035: Pattern.compile("\\D*(\\d++.\\d*)\\D*"); ingo@2035: ingo@2063: public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km"; ingo@2063: public static final String DEFAULT_CSV_W_HEADER = "W [NN + m]"; ingo@2063: public static final String DEFAULT_CSV_Q_HEADER = "Q [m\u00b3/s]"; ingo@2063: public static final String DEFAULT_CSV_Q_DESC_HEADER = "Bezeichnung"; ingo@2068: public static final String DEFAULT_CSV_W_DESC_HEADER = "W/Pegel [cm]"; ingo@2063: public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage"; ingo@2063: public static final String DEFAULT_CSV_GAUGE_HEADER = "Bezugspegel"; ingo@2066: public static final String DEFAULT_CSV_NOT_IN_GAUGE_RANGE = ingo@2066: "außerhalb des gewählten Bezugspegels"; ingo@416: raimund@2176: public static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode"; felix@2284: public static final String JASPER_FILE = "export.waterlevel.pdf.file"; ingo@416: aheinecke@6601: /** The storage that contains all WQKms objects that are calculated.*/ ingo@389: protected List data; ingo@389: aheinecke@6601: /** The storage that contains official fixings if available.*/ aheinecke@6601: protected List officalFixings; ingo@389: teichmann@7077: public WaterlevelExporter() { aheinecke@6941: data = new ArrayList(); ingo@389: } ingo@389: ingo@446: @Override ingo@446: public void generate() ingo@446: throws IOException ingo@446: { ingo@446: logger.debug("WaterlevelExporter.generate"); ingo@446: aheinecke@6601: /* Check for official fixings. They should also be included in the aheinecke@6601: * export but only the calculation result is added with addData */ aheinecke@6601: aheinecke@6601: officalFixings = new ArrayList(); aheinecke@6601: aheinecke@6601: for (Artifact art: collection.getArtifactsByName(STATICWQKMSNAME, context)) { aheinecke@6601: if (art instanceof StaticWQKmsArtifact) { aheinecke@6601: StaticWQKmsArtifact sart = (StaticWQKmsArtifact) art; aheinecke@6601: if (!sart.isOfficial()) { aheinecke@6601: continue; aheinecke@6601: } aheinecke@6601: aheinecke@6601: /* Check that we add the data only once */ aheinecke@6601: WQKms toAdd = sart.getWQKms(); aheinecke@6601: String newName = toAdd.getName(); aheinecke@6601: aheinecke@6601: boolean exists = false; aheinecke@6601: for (WQKms wqkm: officalFixings) { aheinecke@6601: /* The same official fixing could be in two aheinecke@6601: artifacts/outs so let's deduplicate */ aheinecke@6601: if (wqkm.getName().equals(newName)) { aheinecke@6601: exists = true; aheinecke@6601: } aheinecke@6601: } aheinecke@6601: if (!exists) { aheinecke@6601: officalFixings.add(toAdd); aheinecke@6601: logger.debug("Adding additional offical fixing: " + newName); aheinecke@6601: } aheinecke@6601: } aheinecke@6601: } aheinecke@6601: ingo@446: if (facet != null && facet.equals(AbstractExporter.FACET_CSV)) { ingo@446: generateCSV(); ingo@446: } ingo@446: else if (facet != null && facet.equals(FACET_WST)) { ingo@446: generateWST(); ingo@446: } raimund@2176: else if (facet != null && facet.equals(AbstractExporter.FACET_PDF)) { raimund@2176: generatePDF(); raimund@2176: } ingo@446: else { ingo@446: throw new IOException("invalid facet for exporter"); ingo@446: } ingo@446: } ingo@446: ingo@446: sascha@701: @Override sascha@701: protected void addData(Object d) { sascha@709: if (d instanceof CalculationResult) { sascha@709: d = ((CalculationResult)d).getData(); sascha@709: if (d instanceof WQKms []) { sascha@709: data.add((WQKms [])d); sascha@709: } felix@4405: else if (d instanceof WQKmsResult) { felix@4405: data.add(((WQKmsResult) d).getWQKms()); ingo@3462: } sascha@701: } ingo@389: } 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: * ingo@2035: * @param winfo A WINFO Artifact. ingo@2035: * @param wqkms A WQKms object that should be prepared. ingo@2035: */ ingo@2038: protected String getColumnTitle(WINFOArtifact winfo, WQKms wqkms) { felix@5131: logger.debug("WaterlevelExporter.getColumnTitle"); ingo@2035: ingo@2035: String name = wqkms.getName(); ingo@2035: ingo@2035: logger.debug("Name of WQKms = '" + name + "'"); ingo@2035: ingo@2038: if (name.indexOf("W=") >= 0) { ingo@2038: return name; ingo@2038: } ingo@2038: ingo@2035: Matcher m = NUMBERS_PATTERN.matcher(name); ingo@2035: ingo@2035: if (m.matches()) { ingo@2035: String raw = m.group(1); ingo@2035: ingo@2035: try { ingo@2035: 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) { teichmann@5865: nmv = RiverUtils.stripNamedMainValue(nmv); ingo@2596: nmv += "=" + String.valueOf(v); ingo@2035: logger.debug("Set named main value '" + nmv + "'"); ingo@2035: ingo@2038: return nmv; ingo@2035: } ingo@2035: } ingo@2035: catch (NumberFormatException nfe) { ingo@2035: // do nothing here ingo@2035: } ingo@2035: } ingo@2038: ingo@2038: return name; ingo@2035: } ingo@2035: ingo@2035: ingo@2087: protected String getCSVRowTitle(WINFOArtifact winfo, WQKms wqkms) { ingo@2087: logger.debug("WaterlevelExporter.prepareNamedValue"); ingo@2087: ingo@2087: String name = wqkms.getName(); ingo@2087: ingo@2087: logger.debug("Name of WQKms = '" + name + "'"); ingo@2087: teichmann@5865: 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: ingo@2087: 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); ingo@2087: logger.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: ingo@2087: felix@3252: /** felix@3252: * Get a string like 'W=' or 'Q=' with a number following in localized felix@3252: * format. felix@3252: */ ingo@2087: protected String localizeWQKms(WINFOArtifact winfo, WQKms wqkms) { teichmann@5865: WQ_MODE wqmode = RiverUtils.getWQMode(winfo); ingo@2087: Double rawValue = wqkms.getRawValue(); ingo@2087: ingo@2087: if (rawValue == null) { ingo@2087: return wqkms.getName(); ingo@2087: } ingo@2087: ingo@2087: NumberFormat nf = Formatter.getRawFormatter(context); ingo@2087: ingo@2087: if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.WGAUGE) { ingo@2087: return "W=" + nf.format(rawValue); ingo@2087: } ingo@2087: else { ingo@2087: return "Q=" + nf.format(rawValue); ingo@2087: } ingo@2087: } ingo@2087: ingo@2087: sascha@701: @Override ingo@391: protected void writeCSVData(CSVWriter writer) { ingo@391: logger.info("WaterlevelExporter.writeData"); ingo@389: teichmann@5867: WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact)master); felix@3252: boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; felix@3252: boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; teichmann@5865: RiverUtils.WQ_INPUT input teichmann@5867: = RiverUtils.getWQInputMode((D4EArtifact)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: ingo@389: for (WQKms[] tmp: data) { ingo@389: for (WQKms wqkms: tmp) { felix@3252: wQKms2CSV(writer, wqkms, atGauge, isQ); aheinecke@6601: 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]) { aheinecke@6601: /* Calculating upstream we assert that it is aheinecke@6601: * impossible that the direction changes during this aheinecke@6601: * loop */ 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 */ aheinecke@6601: for (WQKms wqkms: officalFixings) { aheinecke@6603: wQKms2CSV(writer, filterWQKms(wqkms, first, last), atGauge, isQ); aheinecke@6603: } aheinecke@6603: } aheinecke@6601: aheinecke@6603: aheinecke@6603: /** 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: * aheinecke@6603: * @param wqkms: The WQKms Object to filter aheinecke@6603: * @param first: The fist kilometer of the range aheinecke@6603: * @param last: The last kilometer of the range aheinecke@6603: * aheinecke@6603: * @return A new WQKms with the relevant data sorted by direction aheinecke@6603: */ aheinecke@6603: private WQKms filterWQKms(WQKms wqkms, Double first, Double last) { aheinecke@6603: if (first.isNaN() || last.isNaN()) { aheinecke@6603: logger.warn("Filtering official fixing without valid first/last."); aheinecke@6603: return wqkms; aheinecke@6603: } aheinecke@6603: int firstIdx = first > last ? wqkms.size() - 1 : 0; aheinecke@6603: int lastIdx = first > last ? 0 : wqkms.size() -1; aheinecke@6603: WQKms filtered = new WQKms (wqkms.size()); aheinecke@6603: filtered.setReferenceSystem(wqkms.getReferenceSystem()); aheinecke@6603: filtered.setName(wqkms.getName()); aheinecke@6603: 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: ingo@389: ingo@2045: protected void writeCSVMeta(CSVWriter writer) { ingo@2045: logger.info("WaterlevelExporter.writeCSVMeta"); ingo@2045: teichmann@5865: // TODO use Access instead of RiverUtils felix@4859: ingo@2045: CallMeta meta = context.getMeta(); ingo@2045: teichmann@5867: D4EArtifact flys = (D4EArtifact) master; ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_RESULT, ingo@2045: CSV_META_RESULT, teichmann@5865: new Object[] { RiverUtils.getRivername(flys) }) ingo@2045: }); ingo@2045: ingo@2045: Locale locale = Resources.getLocale(meta); ingo@2045: DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_CREATION, ingo@2045: CSV_META_CREATION, ingo@2045: new Object[] { df.format(new Date()) }) ingo@2045: }); ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_CALCULATIONBASE, ingo@2045: CSV_META_CALCULATIONBASE, ingo@2045: new Object[] { "" }) // TODO what is required at this place? ingo@2045: }); ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_RIVER, ingo@2045: CSV_META_RIVER, teichmann@5865: new Object[] { RiverUtils.getRivername(flys) }) ingo@2045: }); ingo@2045: teichmann@6101: RangeAccess rangeAccess = new RangeAccess(flys); felix@4859: double[] kms = rangeAccess.getKmRange(); ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_RANGE, ingo@2045: CSV_META_RANGE, ingo@2045: new Object[] { kms[0], kms[kms.length-1] }) ingo@2045: }); ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_GAUGE, ingo@2045: CSV_META_GAUGE, teichmann@5865: new Object[] { RiverUtils.getGaugename(flys) }) ingo@2045: }); ingo@2045: teichmann@5865: RiverUtils.WQ_MODE wq = RiverUtils.getWQMode(flys); teichmann@5865: if (wq == RiverUtils.WQ_MODE.QFREE || wq == RiverUtils.WQ_MODE.QGAUGE) { teichmann@5865: double[] qs = RiverUtils.getQs(flys); teichmann@5865: RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode(flys); ingo@2045: ingo@2423: String data = ""; ingo@2045: teichmann@5865: if ((input == RiverUtils.WQ_INPUT.ADAPTED || teichmann@5865: input == RiverUtils.WQ_INPUT.RANGE) && ingo@2423: qs != null && qs.length > 0) ingo@2423: { ingo@2423: data = String.valueOf(qs[0]); ingo@2423: data += " - " + String.valueOf(qs[qs.length-1]); ingo@2423: } teichmann@5865: 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: } ingo@2045: } ingo@2045: else { ingo@2045: logger.warn("Could not determine Q range!"); ingo@2045: } ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_Q, ingo@2045: CSV_META_Q, ingo@2423: new Object[] {data}) ingo@2045: }); ingo@2045: } ingo@2045: else { teichmann@5865: 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]); ingo@2045: upper = String.valueOf(ws[ws.length-1]); ingo@2045: } ingo@2045: else { ingo@2045: logger.warn("Could not determine W range!"); ingo@2045: } ingo@2045: ingo@2045: writer.writeNext(new String[] { ingo@2045: Resources.getMsg( ingo@2045: meta, ingo@2045: CSV_META_W, ingo@2045: CSV_META_W, ingo@2045: new Object[] { lower, upper }) ingo@2045: }); ingo@2045: } ingo@2045: ingo@2045: writer.writeNext(new String[] { "" }); ingo@2045: } 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: */ ingo@2068: protected void writeCSVHeader( ingo@2068: CSVWriter writer, ingo@2068: boolean atGauge, ingo@2068: boolean isQ ingo@2068: ) { ingo@416: logger.info("WaterlevelExporter.writeCSVHeader"); ingo@416: teichmann@5867: String unit = RiverUtils.getRiver((D4EArtifact) master).getWstUnit().getName(); felix@5133: ingo@2065: if (atGauge) { ingo@2065: writer.writeNext(new String[] { ingo@2065: msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), felix@5133: msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), ingo@2065: msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER), ingo@2068: (isQ ingo@2068: ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER) ingo@2068: : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)), ingo@2065: msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER), ingo@2065: msg(CSV_GAUGE_HEADER, DEFAULT_CSV_GAUGE_HEADER) ingo@2065: }); ingo@2065: } ingo@2065: else { ingo@2065: writer.writeNext(new String[] { ingo@2065: msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), felix@5133: // TODO flys/issue1128 (unit per river) felix@5133: msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), ingo@2065: msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER), ingo@2065: msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER) ingo@2065: }); ingo@2065: } ingo@416: } ingo@416: ingo@416: felix@5112: /** Linearly search for gauge which is valid at km. */ teichmann@5587: private static Gauge findGauge(double km, List gauges) { felix@5112: for (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: teichmann@5587: private static Segment findSegment(double km, List segments) { teichmann@5587: for (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: felix@5112: teichmann@5867: private void writeRow4(CSVWriter writer, double wqkm[], D4EArtifact flys) { felix@5112: NumberFormat kmf = getKmFormatter(); felix@5112: NumberFormat wf = getWFormatter(); felix@5112: NumberFormat qf = getQFormatter(); aheinecke@6607: writer.writeNext(new String[] { aheinecke@6607: kmf.format(wqkm[2]), aheinecke@6607: wf.format(wqkm[0]), aheinecke@6941: qf.format(RiverUtils.roundQ(wqkm[1])), aheinecke@6607: RiverUtils.getLocationDescription(flys, wqkm[2]) aheinecke@6607: }); felix@5112: } felix@5112: felix@5112: /** Write an csv-row at gauge location. */ felix@5112: private void writeRow6(CSVWriter writer, double wqkm[], String wOrQDesc, teichmann@5867: D4EArtifact flys, String gaugeName) { felix@5112: NumberFormat kmf = getKmFormatter(); felix@5112: NumberFormat wf = getWFormatter(); felix@5112: NumberFormat qf = getQFormatter(); felix@5112: felix@5112: writer.writeNext(new String[] { felix@5112: kmf.format(wqkm[2]), felix@5112: wf.format(wqkm[0]), aheinecke@6941: qf.format(RiverUtils.roundQ(wqkm[1])), felix@5112: wOrQDesc, teichmann@5865: RiverUtils.getLocationDescription(flys, wqkm[2]), felix@5112: gaugeName felix@5112: }); felix@5112: } felix@5112: aheinecke@6606: private String getDesc(WQKms wqkms, boolean isQ) aheinecke@6606: { aheinecke@6606: D4EArtifact flys = (D4EArtifact) master; aheinecke@6606: String colDesc = ""; aheinecke@6606: aheinecke@6606: if (flys instanceof WINFOArtifact && isQ) { aheinecke@6606: colDesc = getCSVRowTitle((WINFOArtifact)flys, wqkms); aheinecke@6606: } aheinecke@6606: else if (!isQ) { aheinecke@6606: Double value = RiverUtils.getValueFromWQ(wqkms); aheinecke@6606: colDesc = (value != null) ? aheinecke@6606: Formatter.getWaterlevelW(context).format(value) : null; aheinecke@6606: } aheinecke@6606: aheinecke@6606: if (flys instanceof WINFOArtifact) { aheinecke@6606: if (wqkms != null && wqkms.getRawValue() != null) { aheinecke@6606: WINFOArtifact winfo = (WINFOArtifact) flys; aheinecke@6606: colDesc = RiverUtils.getNamedMainValue(winfo, wqkms.getRawValue()); aheinecke@6606: // For 'W am Pegel' s aheinecke@6606: if (colDesc == null) { aheinecke@6634: Double value = RiverUtils.getValueFromWQ(wqkms); aheinecke@6634: colDesc = (value != null) ? aheinecke@6634: Formatter.getWaterlevelW(context).format(value) : null; aheinecke@6606: } aheinecke@6606: } aheinecke@6606: } aheinecke@6610: if (colDesc != null) { aheinecke@6610: /* Quick hack. Can be removed when database strings are aheinecke@6610: * adapted or left in here as it should never be harmful. */ 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: */ ingo@2068: protected void wQKms2CSV( ingo@2068: CSVWriter writer, ingo@2068: WQKms wqkms, ingo@2068: boolean atGauge, felix@3252: boolean isQ ingo@2068: ) { ingo@389: logger.debug("WaterlevelExporter.wQKms2CSV"); ingo@389: teichmann@4836: // Skip constant data. teichmann@4836: if (wqkms instanceof ConstantWQKms) { teichmann@4836: return; teichmann@4836: } teichmann@4836: ingo@418: NumberFormat kmf = getKmFormatter(); ingo@418: NumberFormat wf = getWFormatter(); ingo@418: NumberFormat qf = getQFormatter(); ingo@418: ingo@389: int size = wqkms.size(); ingo@389: double[] result = new double[3]; ingo@389: teichmann@5867: D4EArtifact flys = (D4EArtifact) master; teichmann@5865: List gauges = RiverUtils.getGauges(flys); teichmann@5865: Gauge gauge = RiverUtils.getGauge(flys); ingo@2066: String gaugeName = gauge.getName(); ingo@2066: String desc = ""; ingo@2066: String notinrange = msg( ingo@2066: CSV_NOT_IN_GAUGE_RANGE, ingo@2066: DEFAULT_CSV_NOT_IN_GAUGE_RANGE); aheinecke@6606: List segments = null; aheinecke@6606: boolean isFixRealize = false; ingo@2066: ingo@2066: double a = gauge.getRange().getA().doubleValue(); ingo@2066: double b = gauge.getRange().getB().doubleValue(); sascha@2144: 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. teichmann@6101: 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; teichmann@5587: Gauge lastGauge = null; teichmann@5587: teichmann@5587: NumberFormat nf = teichmann@5587: Formatter.getFormatter(context.getMeta(), 0, 0); teichmann@5587: teichmann@5587: for (int i = 0; i < size; ++i) { teichmann@5587: result = wqkms.get(i, result); teichmann@5587: double km = result[2]; teichmann@5587: teichmann@5587: if (segments != null) { teichmann@5587: Segment found = lastSegment != null teichmann@5587: && lastSegment.inside(km) teichmann@5587: ? lastSegment teichmann@5587: : 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) { teichmann@5587: Gauge found = lastGauge != null teichmann@5587: && lastGauge.getRange().contains(km) teichmann@5587: ? lastGauge teichmann@5587: : findGauge(km, gauges); teichmann@5587: teichmann@5587: gaugeN = found != null ? found.getName() : notinrange; teichmann@5587: lastGauge = found; felix@5112: } felix@5112: else { felix@4979: // TODO issue1114: Take correct gauge teichmann@5587: gaugeN = km >= a && km <= b ingo@2066: ? gaugeName felix@5112: : notinrange; felix@5112: } aheinecke@6606: writeRow6(writer, result, desc, flys, gaugeN); ingo@2065: } teichmann@5587: } teichmann@5587: 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: sascha@2144: long stopTime = System.currentTimeMillis(); sascha@2144: sascha@2144: if (logger.isDebugEnabled()) { sascha@2144: logger.debug("Writing CSV took " + sascha@2144: (float)(stopTime-startTime)/1000f + " secs."); sascha@2144: } ingo@389: } ingo@418: ingo@418: ingo@418: /** ingo@446: * Generates the output in WST format. ingo@446: */ ingo@446: protected void generateWST() ingo@446: throws IOException ingo@446: { ingo@446: logger.info("WaterlevelExporter.generateWST"); ingo@446: aheinecke@7604: int cols = data.get(0).length + officalFixings.size(); ingo@446: WstWriter writer = new WstWriter(cols); ingo@446: ingo@446: writeWSTData(writer); ingo@446: ingo@446: writer.write(out); ingo@446: } ingo@446: ingo@446: ingo@446: protected void writeWSTData(WstWriter writer) { ingo@446: logger.debug("WaterlevelExporter.writeWSTData"); ingo@446: ingo@749: double[] result = new double[4]; ingo@446: ingo@446: for (WQKms[] tmp: data) { ingo@446: for (WQKms wqkms: tmp) { rrenkert@5105: if (wqkms instanceof ConstantWQKms) { rrenkert@5105: continue; rrenkert@5105: } ingo@446: 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. aheinecke@7604: for (WQKms wqkms: officalFixings) { aheinecke@7604: // To add some spaces here or to add them in the writer,.. aheinecke@7604: writer.addColumn(" " + getDesc(wqkms, true)); aheinecke@7604: aheinecke@7604: // Get all lines from the calculation aheinecke@7604: Map calcLines = writer.getLines(); aheinecke@7604: aheinecke@7604: // All KM values where we have a point for aheinecke@7604: TDoubleArrayList officialKms = wqkms.allKms(); aheinecke@7604: aheinecke@7604: for (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* aheinecke@7604: double km = entry.getKey().doubleValue(); aheinecke@7604: int idx = officialKms.indexOf(km); aheinecke@7604: if (idx != -1) { aheinecke@7604: entry.getValue().add(wqkms.getW(idx), entry.getValue().getQ(0)); aheinecke@7604: } aheinecke@7604: } aheinecke@7604: aheinecke@7604: /* Variant: Interpolate the values aheinecke@7604: // Now get the lines for the of the calculation aheinecke@7604: Map calcLines = writer.getLines(); aheinecke@7604: aheinecke@7604: // Create an interpolater for the official KM -> W relation aheinecke@7604: UnivariateRealFunction wFunc = new LinearInterpolator( aheinecke@7604: ).interpolate(wqkms.allKms().toNativeArray(), aheinecke@7604: wqkms.allWs().toNativeArray()); aheinecke@7604: aheinecke@7604: // Now for each calculated point add the interpolated official aheinecke@7604: for (Map.Entry entry : calcLines.entrySet()) { aheinecke@7604: try { aheinecke@7604: double wVal = wFunc.value(entry.getKey().doubleValue()); aheinecke@7604: // Matching Q's are guranteed otherwise we would aheinecke@7604: // not have an official line aheinecke@7604: entry.getValue().add(wVal, entry.getValue().getQ(0)); aheinecke@7604: } aheinecke@7604: catch (FunctionEvaluationException aode) { aheinecke@7604: // should not happen aheinecke@7604: logger.error("spline interpolation failed", aode); aheinecke@7604: // entry.getValue().add(Double.NaN, entry.getValue().getQ(0)); aheinecke@7604: } aheinecke@7604: }*/ aheinecke@7604: aheinecke@7604: /* Variant: Add all official fixings aheinecke@7604: // Warning the WSTWriter does not handle this properly aheinecke@7604: // as it writes the points for which only a calculation aheinecke@7604: // exists in the first column aheinecke@7604: int size = wqkms.size(); aheinecke@7604: result = new double[4]; aheinecke@7604: for (int i = 0; i < size; i++) { aheinecke@7604: result = wqkms.get(i, result); aheinecke@7604: writer.add(result); aheinecke@7604: } aheinecke@7604: */ aheinecke@7604: } ingo@446: } 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: * ingo@2038: * @param writer The WstWriter. ingo@2038: * @param wqkms The new WST column. ingo@2038: */ ingo@450: protected void addWSTColumn(WstWriter writer, WQKms wqkms) { teichmann@4836: if (wqkms instanceof ConstantWQKms) { teichmann@4836: return; teichmann@4836: } ingo@2038: if (master instanceof WINFOArtifact) { ingo@2038: writer.addColumn(getColumnTitle((WINFOArtifact) master, wqkms)); ingo@2038: } ingo@2038: else { ingo@2038: writer.addColumn(wqkms.getName()); ingo@2038: } ingo@450: } ingo@450: ingo@450: raimund@2176: @Override raimund@2176: protected void writePDF(OutputStream out) { raimund@2176: logger.debug("write PDF"); raimund@2176: WKmsJRDataSource source = createJRData(); raimund@2185: raimund@2185: String jasperFile = Resources.getMsg( raimund@2185: context.getMeta(), raimund@2185: JASPER_FILE, raimund@2185: "/jasper/waterlevel_en.jasper"); raimund@2185: String confPath = Config.getConfigDirectory().toString(); raimund@2185: raimund@2185: raimund@2176: Map parameters = new HashMap(); raimund@2176: parameters.put("ReportTitle", "Exported Data"); raimund@2176: try { raimund@2176: JasperPrint print = JasperFillManager.fillReport( raimund@2185: confPath + jasperFile, raimund@2176: parameters, raimund@2176: source); raimund@2176: JasperExportManager.exportReportToPdfStream(print, out); raimund@2176: } raimund@2176: catch(JRException je) { teichmann@4836: logger.warn("Error generating PDF Report!", je); raimund@2176: } raimund@2176: } raimund@2176: raimund@2176: protected WKmsJRDataSource createJRData() { raimund@2176: WKmsJRDataSource source = new WKmsJRDataSource(); raimund@2176: teichmann@5867: WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact)master); raimund@2176: boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; raimund@2176: 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); raimund@2176: for (WQKms[] tmp: data) { raimund@2176: for (WQKms wqkms: tmp) { raimund@2176: addWKmsData(wqkms, atGauge, isQ, source); aheinecke@6608: 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]) { aheinecke@6608: /* Calculating upstream we assert that it is aheinecke@6608: * impossible that the direction changes during this aheinecke@6608: * loop */ 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 */ aheinecke@6608: for (WQKms wqkms: officalFixings) { aheinecke@6608: addWKmsData(filterWQKms(wqkms, first, last), atGauge, isQ, source); aheinecke@6608: } raimund@2176: return source; raimund@2176: } raimund@2176: raimund@2176: protected void addMetaData(WKmsJRDataSource source) { raimund@2176: CallMeta meta = context.getMeta(); raimund@2176: teichmann@5867: D4EArtifact flys = (D4EArtifact) master; raimund@2176: teichmann@5865: source.addMetaData ("river", RiverUtils.getRivername(flys)); raimund@2176: raimund@2176: Locale locale = Resources.getLocale(meta); raimund@2176: DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); aheinecke@6941: NumberFormat kmf = getKmFormatter(); raimund@2176: raimund@2176: source.addMetaData("date", df.format(new Date())); raimund@2176: teichmann@6101: RangeAccess rangeAccess = new RangeAccess(flys); felix@4859: double[] kms = rangeAccess.getKmRange(); aheinecke@6941: source.addMetaData("range", aheinecke@6941: kmf.format(kms[0]) + " - " + kmf.format(kms[kms.length-1])); raimund@2176: teichmann@5865: source.addMetaData("gauge", RiverUtils.getGaugename(flys)); raimund@2176: raimund@2176: source.addMetaData("calculation", Resources.getMsg( raimund@2176: locale, raimund@2176: PDF_HEADER_MODE, raimund@2176: "Waterlevel")); raimund@2176: } raimund@2176: raimund@2176: protected void addWKmsData( raimund@2176: WQKms wqkms, raimund@2176: boolean atGauge, raimund@2176: boolean isQ, raimund@2176: WKmsJRDataSource source) raimund@2176: { raimund@2176: logger.debug("WaterlevelExporter.addWKmsData"); raimund@2176: teichmann@4836: // Skip constant data. teichmann@4836: if (wqkms instanceof ConstantWQKms) { teichmann@4836: return; teichmann@4836: } teichmann@4836: raimund@2176: NumberFormat kmf = getKmFormatter(); raimund@2176: NumberFormat wf = getWFormatter(); raimund@2176: NumberFormat qf = getQFormatter(); raimund@2176: raimund@2176: int size = wqkms.size(); raimund@2176: double[] result = new double[3]; raimund@2176: teichmann@5867: D4EArtifact flys = (D4EArtifact) master; teichmann@5865: Gauge gauge = RiverUtils.getGauge(flys); raimund@2176: String gaugeName = gauge.getName(); raimund@2176: String desc = ""; raimund@2176: String notinrange = msg( raimund@2176: CSV_NOT_IN_GAUGE_RANGE, raimund@2176: DEFAULT_CSV_NOT_IN_GAUGE_RANGE); raimund@2176: raimund@2176: double a = gauge.getRange().getA().doubleValue(); raimund@2176: double b = gauge.getRange().getB().doubleValue(); raimund@2176: aheinecke@6606: desc = getDesc(wqkms, isQ); raimund@2176: long startTime = System.currentTimeMillis(); raimund@2176: raimund@2176: for (int i = 0; i < size; i ++) { raimund@2176: result = wqkms.get(i, result); raimund@2176: raimund@2176: if (atGauge) { raimund@2176: source.addData(new String[] { raimund@2176: kmf.format(result[2]), raimund@2176: wf.format(result[0]), aheinecke@6941: qf.format(RiverUtils.roundQ(result[1])), raimund@2176: desc, teichmann@5865: RiverUtils.getLocationDescription(flys, result[2]), raimund@2176: result[2] >= a && result[2] <= b raimund@2176: ? gaugeName raimund@2176: : notinrange raimund@2176: }); raimund@2176: } raimund@2176: else { raimund@2176: source.addData(new String[] { raimund@2176: kmf.format(result[2]), raimund@2176: wf.format(result[0]), aheinecke@6941: qf.format(RiverUtils.roundQ(result[1])), raimund@2764: desc, teichmann@5865: RiverUtils.getLocationDescription(flys, result[2]), raimund@2764: result[2] >= a && result[2] <= b raimund@2764: ? gaugeName raimund@2764: : notinrange raimund@2176: }); raimund@2176: } raimund@2176: } raimund@2176: raimund@2176: long stopTime = System.currentTimeMillis(); raimund@2176: raimund@2176: if (logger.isDebugEnabled()) { raimund@2176: logger.debug("Writing PDF data took " + raimund@2176: (float)(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 :