Mercurial > dive4elements > river
view artifacts/src/main/java/org/dive4elements/river/exports/WaterlevelExporter.java @ 6168:2441a8cc8f37
part of issue986: axis label includes correct wst-unit.
author | Felix Wolfsteller <felix.wolfsteller@intevation.de> |
---|---|
date | Mon, 03 Jun 2013 14:52:31 +0200 |
parents | a0078e5e3b39 |
children | c59a23183bc0 |
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde * Software engineering by Intevation GmbH * * This file is Free Software under the GNU AGPL (>=v3) * and comes with ABSOLUTELY NO WARRANTY! Check out the * documentation coming with Dive4Elements River for details. */ package org.dive4elements.river.exports; import java.io.IOException; import java.io.OutputStream; import java.text.DateFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.w3c.dom.Document; import org.apache.log4j.Logger; import au.com.bytecode.opencsv.CSVWriter; import org.dive4elements.river.artifacts.model.ConstantWQKms; import net.sf.jasperreports.engine.JasperExportManager; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.JasperPrint; import net.sf.jasperreports.engine.JRException; import org.dive4elements.artifacts.CallContext; import org.dive4elements.artifacts.CallMeta; import org.dive4elements.artifacts.common.utils.Config; import org.dive4elements.river.model.Gauge; import org.dive4elements.river.artifacts.access.FixRealizingAccess; import org.dive4elements.river.artifacts.access.RangeAccess; import org.dive4elements.river.artifacts.FixationArtifact; import org.dive4elements.river.artifacts.D4EArtifact; import org.dive4elements.river.artifacts.WINFOArtifact; import org.dive4elements.river.artifacts.model.CalculationResult; import org.dive4elements.river.artifacts.model.Segment; import org.dive4elements.river.artifacts.model.WQCKms; import org.dive4elements.river.artifacts.model.WQKms; import org.dive4elements.river.artifacts.model.WKmsJRDataSource; import org.dive4elements.river.artifacts.model.WQKmsResult; import org.dive4elements.river.artifacts.resources.Resources; import org.dive4elements.river.utils.RiverUtils; import org.dive4elements.river.utils.RiverUtils.WQ_MODE; import org.dive4elements.river.utils.Formatter; /** * Generates different output formats (wst, csv, pdf) of data that resulted from * a waterlevel computation. * * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public class WaterlevelExporter extends AbstractExporter { /** The logger used in this exporter.*/ private static Logger logger = Logger.getLogger(WaterlevelExporter.class); public static final String FACET_WST = "wst"; public static final String CSV_KM_HEADER = "export.waterlevel.csv.header.km"; public static final String CSV_W_HEADER = "export.waterlevel.csv.header.w"; public static final String CSV_Q_HEADER = "export.waterlevel.csv.header.q"; public static final String CSV_Q_DESC_HEADER = "export.waterlevel.csv.header.q.desc"; public static final String CSV_W_DESC_HEADER = "export.waterlevel.csv.header.w.desc"; public static final String CSV_LOCATION_HEADER = "export.waterlevel.csv.header.location"; public static final String CSV_GAUGE_HEADER = "export.waterlevel.csv.header.gauge"; public static final String CSV_META_RESULT = "export.waterlevel.csv.meta.result"; public static final String CSV_META_CREATION = "export.waterlevel.csv.meta.creation"; public static final String CSV_META_CALCULATIONBASE = "export.waterlevel.csv.meta.calculationbase"; public static final String CSV_META_RIVER = "export.waterlevel.csv.meta.river"; public static final String CSV_META_RANGE = "export.waterlevel.csv.meta.range"; public static final String CSV_META_GAUGE = "export.waterlevel.csv.meta.gauge"; public static final String CSV_META_Q = "export.waterlevel.csv.meta.q"; public static final String CSV_META_W = "export.waterlevel.csv.meta.w"; public static final String CSV_NOT_IN_GAUGE_RANGE = "export.waterlevel.csv.not.in.gauge.range"; public static final Pattern NUMBERS_PATTERN = Pattern.compile("\\D*(\\d++.\\d*)\\D*"); public static final String DEFAULT_CSV_KM_HEADER = "Fluss-Km"; public static final String DEFAULT_CSV_W_HEADER = "W [NN + m]"; public static final String DEFAULT_CSV_Q_HEADER = "Q [m\u00b3/s]"; public static final String DEFAULT_CSV_Q_DESC_HEADER = "Bezeichnung"; public static final String DEFAULT_CSV_W_DESC_HEADER = "W/Pegel [cm]"; public static final String DEFAULT_CSV_LOCATION_HEADER = "Lage"; public static final String DEFAULT_CSV_GAUGE_HEADER = "Bezugspegel"; public static final String DEFAULT_CSV_NOT_IN_GAUGE_RANGE = "außerhalb des gewählten Bezugspegels"; public static final String PDF_HEADER_MODE = "export.waterlevel.pdf.mode"; public static final String JASPER_FILE = "export.waterlevel.pdf.file"; /** The storage that contains all WQKms objects for the different facets.*/ protected List<WQKms[]> data; public void init(Document request, OutputStream out, CallContext context) { logger.debug("WaterlevelExporter.init"); super.init(request, out, context); this.data = new ArrayList<WQKms[]>(); } @Override public void generate() throws IOException { logger.debug("WaterlevelExporter.generate"); if (facet != null && facet.equals(AbstractExporter.FACET_CSV)) { generateCSV(); } else if (facet != null && facet.equals(FACET_WST)) { generateWST(); } else if (facet != null && facet.equals(AbstractExporter.FACET_PDF)) { generatePDF(); } else { throw new IOException("invalid facet for exporter"); } } @Override protected void addData(Object d) { if (d instanceof CalculationResult) { d = ((CalculationResult)d).getData(); if (d instanceof WQKms []) { data.add((WQKms [])d); } else if (d instanceof WQKmsResult) { data.add(((WQKmsResult) d).getWQKms()); } } } /** * This method is used to prepare the column titles of waterlevel exports. * Titles in this export include the Q value. If a Q value matches a named * main value (as HQ100 or MNQ) this named main value should be used as * title. This method resets the name of the <i>wqkms</i> object if such * named main value fits to the chosen Q. * * @param winfo A WINFO Artifact. * @param wqkms A WQKms object that should be prepared. */ protected String getColumnTitle(WINFOArtifact winfo, WQKms wqkms) { logger.debug("WaterlevelExporter.getColumnTitle"); String name = wqkms.getName(); logger.debug("Name of WQKms = '" + name + "'"); if (name.indexOf("W=") >= 0) { return name; } Matcher m = NUMBERS_PATTERN.matcher(name); if (m.matches()) { String raw = m.group(1); try { double v = Double.valueOf(raw); String nmv = RiverUtils.getNamedMainValue(winfo, v); if (nmv != null && nmv.length() > 0) { nmv = RiverUtils.stripNamedMainValue(nmv); nmv += "=" + String.valueOf(v); logger.debug("Set named main value '" + nmv + "'"); return nmv; } } catch (NumberFormatException nfe) { // do nothing here } } return name; } protected String getCSVRowTitle(WINFOArtifact winfo, WQKms wqkms) { logger.debug("WaterlevelExporter.prepareNamedValue"); String name = wqkms.getName(); logger.debug("Name of WQKms = '" + name + "'"); WQ_MODE wqmode = RiverUtils.getWQMode(winfo); if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.QGAUGE) { return localizeWQKms(winfo, wqkms); } Double v = wqkms.getRawValue(); String nmv = RiverUtils.getNamedMainValue(winfo, v); if (nmv != null && nmv.length() > 0) { nmv = RiverUtils.stripNamedMainValue(nmv); logger.debug("Set named main value '" + nmv + "'"); return nmv; } return localizeWQKms(winfo, wqkms); } /** * Get a string like 'W=' or 'Q=' with a number following in localized * format. */ protected String localizeWQKms(WINFOArtifact winfo, WQKms wqkms) { WQ_MODE wqmode = RiverUtils.getWQMode(winfo); Double rawValue = wqkms.getRawValue(); if (rawValue == null) { return wqkms.getName(); } NumberFormat nf = Formatter.getRawFormatter(context); if (wqmode == WQ_MODE.WFREE || wqmode == WQ_MODE.WGAUGE) { return "W=" + nf.format(rawValue); } else { return "Q=" + nf.format(rawValue); } } @Override protected void writeCSVData(CSVWriter writer) { logger.info("WaterlevelExporter.writeData"); WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact)master); boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode((D4EArtifact)master); writeCSVMeta(writer); writeCSVHeader(writer, atGauge, isQ); for (WQKms[] tmp: data) { for (WQKms wqkms: tmp) { wQKms2CSV(writer, wqkms, atGauge, isQ); } } } protected void writeCSVMeta(CSVWriter writer) { logger.info("WaterlevelExporter.writeCSVMeta"); // TODO use Access instead of RiverUtils CallMeta meta = context.getMeta(); D4EArtifact flys = (D4EArtifact) master; writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_RESULT, CSV_META_RESULT, new Object[] { RiverUtils.getRivername(flys) }) }); Locale locale = Resources.getLocale(meta); DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_CREATION, CSV_META_CREATION, new Object[] { df.format(new Date()) }) }); writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_CALCULATIONBASE, CSV_META_CALCULATIONBASE, new Object[] { "" }) // TODO what is required at this place? }); writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_RIVER, CSV_META_RIVER, new Object[] { RiverUtils.getRivername(flys) }) }); RangeAccess rangeAccess = new RangeAccess(flys); double[] kms = rangeAccess.getKmRange(); writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_RANGE, CSV_META_RANGE, new Object[] { kms[0], kms[kms.length-1] }) }); writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_GAUGE, CSV_META_GAUGE, new Object[] { RiverUtils.getGaugename(flys) }) }); RiverUtils.WQ_MODE wq = RiverUtils.getWQMode(flys); if (wq == RiverUtils.WQ_MODE.QFREE || wq == RiverUtils.WQ_MODE.QGAUGE) { double[] qs = RiverUtils.getQs(flys); RiverUtils.WQ_INPUT input = RiverUtils.getWQInputMode(flys); String data = ""; if ((input == RiverUtils.WQ_INPUT.ADAPTED || input == RiverUtils.WQ_INPUT.RANGE) && qs != null && qs.length > 0) { data = String.valueOf(qs[0]); data += " - " + String.valueOf(qs[qs.length-1]); } else if (input == RiverUtils.WQ_INPUT.SINGLE && qs != null){ data = String.valueOf(qs[0]); for (int i = 1; i < qs.length; i++) { data += ", " + String.valueOf(qs[i]); } } else { logger.warn("Could not determine Q range!"); } writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_Q, CSV_META_Q, new Object[] {data}) }); } else { double[] ws = RiverUtils.getWs(flys); String lower = ""; String upper = ""; if (ws != null && ws.length > 0) { lower = String.valueOf(ws[0]); upper = String.valueOf(ws[ws.length-1]); } else { logger.warn("Could not determine W range!"); } writer.writeNext(new String[] { Resources.getMsg( meta, CSV_META_W, CSV_META_W, new Object[] { lower, upper }) }); } writer.writeNext(new String[] { "" }); } /** * Write the header, with different headings depending on whether at a * gauge or at a location. */ protected void writeCSVHeader( CSVWriter writer, boolean atGauge, boolean isQ ) { logger.info("WaterlevelExporter.writeCSVHeader"); String unit = RiverUtils.getRiver((D4EArtifact) master).getWstUnit().getName(); if (atGauge) { writer.writeNext(new String[] { msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER), (isQ ? msg(CSV_Q_DESC_HEADER, DEFAULT_CSV_Q_DESC_HEADER) : msg(CSV_W_DESC_HEADER, DEFAULT_CSV_W_DESC_HEADER)), msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER), msg(CSV_GAUGE_HEADER, DEFAULT_CSV_GAUGE_HEADER) }); } else { writer.writeNext(new String[] { msg(CSV_KM_HEADER, DEFAULT_CSV_KM_HEADER), // TODO flys/issue1128 (unit per river) msg(CSV_W_HEADER, DEFAULT_CSV_W_HEADER, new Object[] { unit }), msg(CSV_Q_HEADER, DEFAULT_CSV_Q_HEADER), msg(CSV_LOCATION_HEADER, DEFAULT_CSV_LOCATION_HEADER) }); } } /** Linearly search for gauge which is valid at km. */ private static Gauge findGauge(double km, List<Gauge> gauges) { for (Gauge gauge: gauges) { if (gauge.getRange().contains(km)) { return gauge; } } return null; } private static Segment findSegment(double km, List<Segment> segments) { for (Segment segment: segments) { if (segment.inside(km)) { return segment; } } return null; } private void writeRow4(CSVWriter writer, double wqkm[], D4EArtifact flys) { NumberFormat kmf = getKmFormatter(); NumberFormat wf = getWFormatter(); NumberFormat qf = getQFormatter(); writer.writeNext(new String[] { kmf.format(wqkm[2]), wf.format(wqkm[0]), qf.format(wqkm[1]), RiverUtils.getLocationDescription(flys, wqkm[2]) }); } /** Write an csv-row at gauge location. */ private void writeRow6(CSVWriter writer, double wqkm[], String wOrQDesc, D4EArtifact flys, String gaugeName) { NumberFormat kmf = getKmFormatter(); NumberFormat wf = getWFormatter(); NumberFormat qf = getQFormatter(); writer.writeNext(new String[] { kmf.format(wqkm[2]), wf.format(wqkm[0]), qf.format(wqkm[1]), wOrQDesc, RiverUtils.getLocationDescription(flys, wqkm[2]), gaugeName }); } /** * Write "rows" of csv data from wqkms with writer. */ protected void wQKms2CSV( CSVWriter writer, WQKms wqkms, boolean atGauge, boolean isQ ) { logger.debug("WaterlevelExporter.wQKms2CSV"); // Skip constant data. if (wqkms instanceof ConstantWQKms) { return; } NumberFormat kmf = getKmFormatter(); NumberFormat wf = getWFormatter(); NumberFormat qf = getQFormatter(); int size = wqkms.size(); double[] result = new double[3]; D4EArtifact flys = (D4EArtifact) master; List<Gauge> gauges = RiverUtils.getGauges(flys); Gauge gauge = RiverUtils.getGauge(flys); String gaugeName = gauge.getName(); String desc = ""; String notinrange = msg( CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); double a = gauge.getRange().getA().doubleValue(); double b = gauge.getRange().getB().doubleValue(); if (flys instanceof WINFOArtifact && isQ) { desc = getCSVRowTitle((WINFOArtifact)flys, wqkms); } else if (!isQ) { Double value = RiverUtils.getValueFromWQ(wqkms); desc = value != null ? Formatter.getWaterlevelW(context).format(value) : null; } long startTime = System.currentTimeMillis(); String colDesc = desc; List<Segment> segments = null; boolean isFixRealize = false; if (flys instanceof WINFOArtifact) { if (wqkms != null && wqkms.getRawValue() != null) { WINFOArtifact winfo = (WINFOArtifact) flys; colDesc = RiverUtils.getNamedMainValue(winfo, wqkms.getRawValue()); // For 'W am Pegel' s if (colDesc == null) { colDesc = ((D4EArtifact)master).getDataAsString("wq_single"); } } } else if (flys instanceof FixationArtifact) { // Get W/Q input per gauge for this case. FixRealizingAccess fixAccess = new FixRealizingAccess(flys); segments = fixAccess.getSegments(); if (segments != null && !segments.isEmpty()) { isFixRealize = true; } } if (atGauge) { // "At gauge" needs more output. // Kms tend to be close together so caching the last sector // is a good time saving heuristic. Segment lastSegment = null; Gauge lastGauge = null; NumberFormat nf = Formatter.getFormatter(context.getMeta(), 0, 0); for (int i = 0; i < size; ++i) { result = wqkms.get(i, result); double km = result[2]; if (segments != null) { Segment found = lastSegment != null && lastSegment.inside(km) ? lastSegment : findSegment(km, segments); if (found != null) { colDesc = nf.format(found.getValues()[0]); } lastSegment = found; } String gaugeN; if (isFixRealize) { Gauge found = lastGauge != null && lastGauge.getRange().contains(km) ? lastGauge : findGauge(km, gauges); gaugeN = found != null ? found.getName() : notinrange; lastGauge = found; } else { // TODO issue1114: Take correct gauge gaugeN = km >= a && km <= b ? gaugeName : notinrange; } writeRow6(writer, result, colDesc, flys, gaugeN); } } else { // Not at gauge. for (int i = 0; i < size; ++i) { result = wqkms.get(i, result); writeRow4(writer, result, flys); } } long stopTime = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug("Writing CSV took " + (float)(stopTime-startTime)/1000f + " secs."); } } /** * Generates the output in WST format. */ protected void generateWST() throws IOException { logger.info("WaterlevelExporter.generateWST"); int cols = data.get(0).length; WstWriter writer = new WstWriter(cols); writeWSTData(writer); writer.write(out); } protected void writeWSTData(WstWriter writer) { logger.debug("WaterlevelExporter.writeWSTData"); double[] result = new double[4]; for (WQKms[] tmp: data) { for (WQKms wqkms: tmp) { if (wqkms instanceof ConstantWQKms) { continue; } int size = wqkms != null ? wqkms.size() : 0; addWSTColumn(writer, wqkms); for (int i = 0; i < size; i++) { result = wqkms.get(i, result); writer.add(result); } if (wqkms instanceof WQCKms) { addWSTColumn(writer, wqkms); for (int c = 0; c < size; c++) { result = wqkms.get(c, result); writer.addCorrected(result); } } } } } /** * This method is used to register a new column at <i>writer</i>. The name / * title of the column depends on the Q or W value of <i>wqkms</i>. If a Q * was selected and the Q fits to a named main value, the title is set to * the named main value. Otherwise, the name returned by * <i>WQKms.getName()</i> is set. * * @param writer The WstWriter. * @param wqkms The new WST column. */ protected void addWSTColumn(WstWriter writer, WQKms wqkms) { if (wqkms instanceof ConstantWQKms) { return; } if (master instanceof WINFOArtifact) { writer.addColumn(getColumnTitle((WINFOArtifact) master, wqkms)); } else { writer.addColumn(wqkms.getName()); } } /** * */ @Override protected void writePDF(OutputStream out) { logger.debug("write PDF"); WKmsJRDataSource source = createJRData(); String jasperFile = Resources.getMsg( context.getMeta(), JASPER_FILE, "/jasper/waterlevel_en.jasper"); String confPath = Config.getConfigDirectory().toString(); Map parameters = new HashMap(); parameters.put("ReportTitle", "Exported Data"); try { JasperPrint print = JasperFillManager.fillReport( confPath + jasperFile, parameters, source); JasperExportManager.exportReportToPdfStream(print, out); } catch(JRException je) { logger.warn("Error generating PDF Report!", je); } } protected WKmsJRDataSource createJRData() { WKmsJRDataSource source = new WKmsJRDataSource(); WQ_MODE mode = RiverUtils.getWQMode((D4EArtifact)master); boolean atGauge = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.WGAUGE; boolean isQ = mode == WQ_MODE.QGAUGE || mode == WQ_MODE.QFREE; addMetaData(source); for (WQKms[] tmp: data) { for (WQKms wqkms: tmp) { addWKmsData(wqkms, atGauge, isQ, source); } } return source; } protected void addMetaData(WKmsJRDataSource source) { CallMeta meta = context.getMeta(); D4EArtifact flys = (D4EArtifact) master; source.addMetaData ("river", RiverUtils.getRivername(flys)); Locale locale = Resources.getLocale(meta); DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale); source.addMetaData("date", df.format(new Date())); RangeAccess rangeAccess = new RangeAccess(flys); double[] kms = rangeAccess.getKmRange(); source.addMetaData("range", kms[0] + " - " + kms[kms.length-1]); source.addMetaData("gauge", RiverUtils.getGaugename(flys)); source.addMetaData("calculation", Resources.getMsg( locale, PDF_HEADER_MODE, "Waterlevel")); } protected void addWKmsData( WQKms wqkms, boolean atGauge, boolean isQ, WKmsJRDataSource source) { logger.debug("WaterlevelExporter.addWKmsData"); // Skip constant data. if (wqkms instanceof ConstantWQKms) { return; } NumberFormat kmf = getKmFormatter(); NumberFormat wf = getWFormatter(); NumberFormat qf = getQFormatter(); int size = wqkms.size(); double[] result = new double[3]; D4EArtifact flys = (D4EArtifact) master; Gauge gauge = RiverUtils.getGauge(flys); String gaugeName = gauge.getName(); String desc = ""; String notinrange = msg( CSV_NOT_IN_GAUGE_RANGE, DEFAULT_CSV_NOT_IN_GAUGE_RANGE); double a = gauge.getRange().getA().doubleValue(); double b = gauge.getRange().getB().doubleValue(); if (flys instanceof WINFOArtifact && isQ) { desc = getCSVRowTitle((WINFOArtifact)flys, wqkms); } else if (!isQ) { Double value = RiverUtils.getValueFromWQ(wqkms); desc = value != null ? Formatter.getWaterlevelW(context).format(value) : null; } long startTime = System.currentTimeMillis(); for (int i = 0; i < size; i ++) { result = wqkms.get(i, result); if (atGauge) { source.addData(new String[] { kmf.format(result[2]), wf.format(result[0]), qf.format(result[1]), desc, RiverUtils.getLocationDescription(flys, result[2]), result[2] >= a && result[2] <= b ? gaugeName : notinrange }); } else { source.addData(new String[] { kmf.format(result[2]), wf.format(result[0]), qf.format(result[1]), desc, RiverUtils.getLocationDescription(flys, result[2]), result[2] >= a && result[2] <= b ? gaugeName : notinrange }); } } long stopTime = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug("Writing PDF data took " + (float)(stopTime-startTime)/1000f + " secs."); } } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :